(非常全面的干货)Python接口自动化测试框架实战开发

news/2024/4/15 14:56:00
一丶叙述

1.项目介绍

整个项目分为四个部分:接口基础丶接口开发丶Unittest与接口测试结合以及接口自动化框架从设计到开发

接口基础包括:HTTP接口 / 常见接口 / 接口工具 / 接口基础知识

接口开发:通过Django来开发get/post接口

Unittest与接口测试结合:unittest应用 / 断言 / requests引入 / HTMLTestRunner / case的管理

接口自动化框架从设计到开发:如何设计框架 / 封装工具类 / 重构基类 / 错误调试 / 结果收集以及处理 / 解决数据依赖 / 结果统计及报告发送

项目整体思路:通过对接口数据文档的读写操作,来获取文档中case的所有数据,然后通过requests模块来发送请求获取的响应数据,通过返回的响应数据中的某个标志性字段的值来判断是否测试成功或者失败,最后将测试的结果数据写入到测试文档或者是html页面又或者是将结果以邮件的形式发送到指定邮箱,这是整个大框架思路,要完成这一系列自动化的测试框架,则需要有一定的python代码基础,博主这里只是粗略的叙述了思路,有很多地方就不细说了比如数据依赖等就请大家慢慢的阅读吧

2.测试报告效果预览

  • unittest和HTMLTestRunner结合生成报告(新版本的)

  • unittest和HTMLTestRunner结合生成报告(经典版本的)

  • 测试报告邮件通知

二丶接口基础知识

1.什么是接口

连接前后端以及移动端,通俗来说就是前端和后端之间的桥梁,比如网站需要去调用银行丶微信及支付宝的接口来完成业务需求

2.接口的种类

外部接口和内部接口;内部接口又分为上层服务与下层服务以及同级服务

3.接口的分类

请求方式:post丶get丶delete丶put

4.为什么要做接口测试

原因:不同端的工作进度肯定是不一致的,那么就需要对最开始开发出来的接口进行测试;对于项目来说缩短项目周期,提高开发效率以及提高系统的健壮性

5.接口测试流程

需求讨论——需求评审——场景设计——用例设计——数据准备——执行

6.为什么要设计测试用例

  • 理清思路,避免侧漏
  • 提高测试效率
  • 跟进测试进度
  • 告诉领导做过
  • 跟进重复重复性工作

7.用例设计分类

功能用例测试:测试功能是否正常丶测试功能是否按照接口文档实现

逻辑用例设计:是否存在依赖业务,例如有些操作是需要用户登录成功的状态下才能进行的操作

异常测试用例设计:参数异常以及数据异常;参数异常包括关键字参数丶参数为空丶多参数丶少参数丶错误参数,数据异常包括关键字数据丶数据为空丶长度不一致丶错误数据

安全测试用例设计:cookie丶header丶唯一识别码

三丶接口测试工具

1.接口测试工具分类

  • 抓取接口工具

httpwatch:集成于IE和Firefox浏览器中,在其他浏览器中无法使用,查看数据也比较麻烦

wireshark:只要是经过电脑的所有请求都会去抓取,导致数据量比较庞大,看数据也比较麻烦

fiddler:轻量级抓包工具,功能比较全,只会记录http请求不会像wireshark工具记录tcp和udp等请求

  • 测试接口工具:

loadrunner:不仅仅是性能测试工具,由于该工具几乎都是基于http请求,所以也可以用来测试接口

fiddler:它除了可以抓包还可以向接口发送各种请求

soapui:接口和自动化测试工具,功能也比较强大

jmeter:跟loadrunner一样不仅仅是做性能测试,也可以对接口进行测试

四丶Fiddler的使用

1.抓取不同类型接口数据(http以及https)

  • 查看windows本机的IP

  • 配置fiddler

  • 需要保证要抓取的手机与电脑保持同一网段,
  • 这里使用逍遥模拟器模拟安卓手机,修改手机网络

  • 在高级选项中设置手动代理IP为windows本机IP地址,端口设置与fiddler抓取端口保持一致

  • 再安卓手机中打开知乎app,抓取知乎app的http服务的数据

  • 现在的移动app都是基于https请求的,所以需要在fiddler中设置https请求

  • 然后在手机端浏览器中访问windows电脑IP+port,进行网络安全证书的下载安装

  • 点击下面一个下载证书

  • 然后设置密码即可

  • 证书安装成功后,重新打开知乎app,则成功抓取https请求的数据

  • 在知乎app中随便对一文章进行评论,抓取该app评论接口

2.数据模拟以及过滤规则

  • 如下图进行选择要过滤的hosts类型,并在输入框添加要过滤的hosts即可

  • 对知乎上的一篇文章进行回答后,获取接口,查看发送的post请求数据中的content字段内容也就是回答的内容

  • 然后进行数据模拟,也就是点击fiddler软件上的replay对接口进行post请求数据的而二次发送,由于知乎这边设定对一个问题只能进行一次回答,所以知乎服务器返回的json数据提示我们失败,同时也说明对接口进行二次数据发送成功,只是规则逻辑失败

3.如何模拟接口响应数据

  • 首先第一步,访问知乎app热榜,在fiddler软件中获取接口查看服务器响应的json格式数据,从服务器返回的json数据看出热榜标题字段名为title_area

  • 然后选择服务器返回的数据类型为TextView,点击.View in Notepad即打开数据记事本,如下图在记事本中找到title_area字段的内容,该字段内容进行了将中文转换为一串字符串

  • 将记事本中的title_area字段的数据修改为this is a test for cdtaogang

  • 点击文件——另存为保存到桌面

  • 回到fiddler中,左侧选中热榜接口,右侧选中AutoResponder,在此窗口下点击Add Rule将左侧的接口添加进去,在右侧下方导入保存在桌面的zhihu_hot.htm文件,最后点击sava保存

  • 回到知乎app中刷新当前热榜页面,则成功返回修改的热榜标题

4.使用fiddler进行评论接口测试

  • 对一篇文章进行评论,抓取评论接口,因为get请求的接口测试太简单,所以博主这里选择评论接口即POST请求方式

  • 右击评论接口选择copy复制接口的url地址

  • 右侧选择Composer,将复制的评论接口url粘贴到地址栏,并选择POST请求方式

  • 因为评论接口涉及到用户身份验证也就是登录后才能进行评论的,所以需要将comments接口中request headers请求头中的所有请求数据以及请求数据中的TextView的值进行复制

请求头数据

请求体数据

  • 将上面复制的请求头和请求体数据分别粘贴到如下输入框中,点击Execute执行发送,然后在左侧则出现了另一个comments接口数据

  • 查看该comments接口,服务器返回的响应数据中与第一个comments接口一致,说明接口测试成功

五丶unittest使用

1.unittest简单使用

  • 在IDE中使用python的环境随便创建个py文件,需要注意的是该py文件的名字不能是test.py,否在运行时会出错,unittest包是python自带的不需要下载安装,代码如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/17 13:10'

  4. import unittest

  5. class TestMethod(unittest.TestCase):

  6. @classmethod

  7. def setUpClass(cls):

  8. print("Method before class execution")

  9. @classmethod

  10. def tearDownClass(cls):

  11. print("Method after class execution")

  12. def setUp(self):

  13. print("------setUp------")

  14. def tearDown(self):

  15. print("------tearDown------")

  16. def test_01(self):

  17. print("First test method")

  18. def test_02(self):

  19. print("The second test method")

  20. if __name__ == '__main__':

  21. unittest.main()

  • 直接run运行以上代码

2.unittest和request重构封装

说明:使用requests模块对接口url地址发送请求,通过unittest测试框架进行case测试

  • 首先博主在逍遥安卓模拟器中下载了一个看书app,通过fiddler对app上的某一接口进行获取,之所以选择对此app进行接口测试,是因为该app的所有接口全是POST请求

  • 在PyCharm下新建工程目录,目录下创建base包,在包下创建一个demo.py文件以及test_method.py文件,用于使用unittest框架来测试以上app接口

  • 在demo.py文件中,使用requests get以及post方法进行了封装,主要是根据传递的参数method来对get以及post方法进行分别调用而已,具体实现如下
 
  1. import requests

  2. class RunMain:

  3. def send_get(self,url,data):

  4. res = requests.get(url=url,data=data).json()

  5. return res

  6. def send_post(self,url,data):

  7. res = requests.post(url=url,data=data).json()

  8. return res

  9. def run_main(self,url,method,data=None):

  10. res = None

  11. if method == 'GET':

  12. res = self.send_get(url,data)

  13. else:

  14. res = self.send_post(url,data)

  15. return res

  • 在test_method.py文件中则创建测试类以及test方法,在test方法中调用demo.py中的run_main方法,即使用requests模块向传递的接口url地址和请求方式以及请求体发送对应的请求,这里使用setUp方法则是利用其优先调用而对RunMain类进行实例化
 
  1. import unittest

  2. import json

  3. import HtmlTestRunner

  4. from .demo import RunMain

  5. class TestMethod(unittest.TestCase):

  6. def setUp(self):

  7. self.run = RunMain()

  8. def test_01(self):

  9. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  10. data = {

  11. "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",

  12. "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",

  13. "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",

  14. "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",

  15. "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"

  16. }

  17. res1 = self.run.run_main(url, "POST", json.dumps(data))

  18. print(res1)

  19. def test_02(self):

  20. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  21. data = {

  22. }

  23. res2 = self.run.run_main(url, 'POST', data)

  24. print(res2)

  25. if __name__ == '__main__':

  26. unittest.main()

  • 运行test_method模块,查看测试接口,test_02则是错误测试

3.unittest中assert的使用

  • 首先根据返回的结果字典dict数据中的status状态值来判断测试是否通过或者失败,逻辑很基础就不细说了
 
  1. class TestMethod(unittest.TestCase):

  2. def setUp(self):

  3. self.run = RunMain()

  4. def test_01(self):

  5. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  6. data = {

  7. "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",

  8. "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",

  9. "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",

  10. "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",

  11. "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"

  12. }

  13. res1 = self.run.run_main(url, "POST", json.dumps(data))

  14. # print(type(res1))

  15. # print(res1['pub'])

  16. # print(type(res1['pub']))

  17. if res1['pub']['status'] == 0:

  18. print("测试通过")

  19. else:

  20. print("测试失败")

  21. print(res1)

  22. def test_02(self):

  23. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  24. data = {

  25. }

  26. res2 = self.run.run_main(url, 'POST', data)

  27. if res2['pub']['status'] == 0:

  28. print("测试通过")

  29. else:

  30. print("测试失败")

  31. print(res2)

  32. if __name__ == '__main__':

  33. unittest.main()

  • 运行以上代码,查看结果与预期一样

  • 将if判断代码更换成unittest模块中的assert断言进行判断,这里使用assertEqual方法来判断两个值是否相等,当两个值相等则返回OK,当不相同时返回assertEqual方法msg变量自定义的值
 
  1. class TestMethod(unittest.TestCase):

  2. def setUp(self):

  3. self.run = RunMain()

  4. def test_01(self):

  5. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  6. data = {

  7. "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",

  8. "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",

  9. "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",

  10. "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",

  11. "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"

  12. }

  13. res1 = self.run.run_main(url, "POST", json.dumps(data))

  14. # print(type(res1))

  15. # print(res1['pub'])

  16. # print(type(res1['pub']))

  17. # if res1['pub']['status'] == 0:

  18. # print("测试通过")

  19. # else:

  20. # print("测试失败")

  21. self.assertEqual(res1['pub']['status'], 0, "测试失败")

  22. print(res1)

  23. def test_02(self):

  24. url = 'http://api.ishugui.com/asg/portal/call/265.do'

  25. data = {

  26. }

  27. res2 = self.run.run_main(url, 'POST', data)

  28. # if res2['pub']['status'] == 0:

  29. # print("测试通过")

  30. # else:

  31. # print("测试失败")

  32. self.assertEqual(res2['pub']['status'], 0, "测试失败")

  33. print(res2)

  34. if __name__ == '__main__':

  35. unittest.main()

  • 测试查看结果,断言失败,测试结果如下很清晰

4.unittest中case的管理及运用

  • 在测试一些接口时,有些接口的返回数据需要在下一个接口进行使用,所以需要定义全局变量,方便每个case都能够得着,当在test_01中定义全局变量userid,然后在test_02中进行打印

  • 在unittest中,是按照字母数字来进行case先后执行顺序的,将test_01改为test_03后,运行代码后,会提示test_02中的userid未定义,原因是程序先去执行了test_02这个case,所以出现该提示是正常的

  • 当在测试代码中有很多case时,我想跳过某个case,则在该case方法上定义unittest的skip方法装饰器,并需要传递此方法名作为实参进行传递

  • 除了在if __name__ == '__main__'中使用unittest.main方法执行所有的case以外,还可以将要测试的case添加到unittest.TestSuite集合中执行想要执行的case,若想要全部都执行则需要一个一个的添加

5.unittest和HTMLTestRunner结合生成报告(博主这里给大家展现两种)

第一种:比较新版本的htmltestrunner报告

  • 安装HTMLTestRunner

  • 然后将下载好的whl文件放在你的项目环境的Scripts目录下

  • 最后在Terminal终端或者cmd终端中进入以上目录,执行如下命令即可

  • 安装成功后,即在以下路径中可以找到安装的HTMLTestRunner的包了

  • 在if __name__ == '__main__'中只需要调用HtmlTestRunner模块中的HtmlTestRunner类,向该类传递报告标题参数值即可,其他均默认,需要注意的时启动文件run为当前的py文件,如果是Unittests开头的启动文件,则不会运行if __name__ == '__main__'下的代码,只会执行unittest框架的setUp以及test开头的case代码

  • 运行test_method.py文件,成功在base目录下创建reports目录,并在该目录下生成对应时间的测试报告

  • 打开reports目录下生成的html测试报告,查看测试内容,与预期设定一样,test_02失败test_03成功,说明一下报告中的乱码为中文

第二种:比较经典版本的htmltestrunner报告

  • 为了方便演示效果,博主在testItems项目目录下,创建base2的模块,将base模块下的demo.py和test_method.py文件拷贝到base2目录下并将test_method.py命令为test_method2.py免得搞混淆,然后在base2目录下新建HTMLTestRunner.py文件用于存放其源码,目录结构如下

  • 紧接着到http://tungwaiyip.info/software/HTMLTestRunner_0_8_2/HTMLTestRunner.py 地址中将HTMLTestRunner.py的代码全选拷贝,然后粘贴到base2/HTMLTestRunner.py文件中,因为该版本的HTMLTestRunner代码是以py2进行编写的,环境使用的是py3,所以需要进行修改,修改的内容如下
 
  1. 第94行, 将import StringIO修改成import io

  2. 第539行,将self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer = io.StringIO()

  3. 第642行,将if not rmap.has_key(cls):修改成if not cls in rmap:

  4. 第631行,将print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改成print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))

  5. 第766行,将uo = o.decode('latin-1')修改成uo = e

  6. 第775行,将ue = e.decode('latin-1')修改成ue = e

  • 在test_method2模块中首先需要从base2模块中去导入HTMLTestRunner文件,然后if __name__ == '__main__'中,需要创建一个文件源,同样是调用HTMLTestRunner模块中的HTMLTestRunner类,不同的是需要将创建的文件源传递给实例属性stream变量

  • 运行test_method2.py,成功在上一级report目录下生成html_report.html报告文件

  • 打开html_report.html测试报告,测试结果与代码设定一致

六丶mock服务入门到实战

1.mock简介

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法,mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个mock对象来模拟对象的行为即就是模拟fiddler返回接口响应数据的一个过程。

2.mock安装

  • 在终端使用pip进行安装即可

3.在case中通过底层函数实现mock

  • 在test_method模块中导入mock,然后在test_03函数中通过以下代码设置返回的return_value的值为请求的data数据
 
  1. mock_data = mock.Mock(return_value=data)

  2. print(mock_data)

  • run运行Unittests in test_method.py,打印出Mock id的值

  • 将调用run_main方法的值设定为mock_data,即print(res1)则表示打印请求的data数据的值,因为res1的数据不再是接口返回的响应数据,则arrest断言是会提示报错的,这是正常的

4.重构封装mock服务

  • 在base目录下创建mock_demo.py文件,构造一个mock_test方法,该方法就是将test_03方法中self.run.run_main = mock.Mock(return_value=data) 和 res1 = self.run.run_main(url, "POST", json.dumps(data))方法的调用进行了封装成为test_02和test_03方法通用的一个方法,上一步骤中的代码mock_data = mock.Mock(return_value=data) 和self.run.run_main = mock_data,即就相当于self.run.run_main = mock.Mock(return_value=data)而已,都是python基本的调用封装基础知识,mock_demo.py中的代码如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/20 16:26'

  4. from mock import mock

  5. import json

  6. def mock_test(mock_method, request_data, url, method, response_data):

  7. """

  8. :param mock_method:

  9. :param request_data:

  10. :param url:

  11. :param method:

  12. :param response_data:

  13. :return: res

  14. """

  15. mock_method = mock.Mock(return_value=response_data)

  16. print('mock_method:', mock_method)

  17. res = mock_method(url, method, json.dumps(request_data))

  18. return res

  • 那么在test_03方法中,如下进行调用即可
 
  1. res1 = mock_test(self.run.run_main, data, url, 'POST', 'ssssssss')

  2. print('res1:', res1)

  • 运行Unittests in test_method.py,查看运行结果和博主设定一样成功返回自定义的response_data数据

七丶接口自动化框架设计到开发

1.如何设计一个接口自动化测试框架

根据接口地址丶接口类型丶请求数据丶预期结果来进行设计,对于需要登录后才能进行操作的接口那么则需要进行header cookie等数据的传递,自动化测试的难点就是数据依赖。

2.python操作excel获得内容

  • 首先python操作excel,需要安装两个包,分别是xlrd和xlwt这两个库,xlrd这个库是负责读取excel数据的,而xlwt库是负责向excel写入数据的

  • 在项目目录下创建utils工具包,在该包下创建op_excel.py文件,在该文件中通过导入xlrd包,对excel表的数据进行读取操作

3.重构操作excel函数

  • 根据上一步骤读取excel表的内容代码后,进行了一个简单的封装,提高代码的通用性,过程相当的简单,实现代码如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/20 17:33'

  4. import xlrd

  5. data = xlrd.open_workbook("../test_data/rs.xls")

  6. tables = data.sheets()[0] # 获取表格数据对象

  7. print(tables.nrows) # 打印表格行数

  8. print(tables.cell_value(0,0)) # 打印excel表格数据,需要传递数据所在的坐标(x,y)

  9. print(tables.cell_value(0,1))

  10. print("*"*50+"封装前后数据对比"+"*"*50)

  11. class operationExcel(object):

  12. def __init__(self, file_path="../test_data/rs.xls", sheet_id=0):

  13. self.file_path = file_path

  14. self.sheet_id = sheet_id

  15. self.data = self.get_data()

  16. def get_data(self):

  17. data = xlrd.open_workbook(self.file_path)

  18. tables = data.sheets()[self.sheet_id]

  19. return tables

  20. def get_rows(self):

  21. """获取单元格的排数"""

  22. return self.data.nrows

  23. def get_cell_value(self, x=0, y=0):

  24. """获取某个单元格的数据"""

  25. return self.data.cell_value(x, y)

  26. if __name__ == '__main__':

  27. print(operationExcel().get_rows())

  28. print(operationExcel().get_cell_value())

  29. print(operationExcel().get_cell_value(0,1))

  • 运行op_excel.py文件后,结果与封装之前代码结果一致,表示重构封装代码成功

4.学习操作json文件

  • 自定义一个登录的json文件名为login.json,文件内容如下,存放在test_data目录下

  • 在utils工具包下创建op_json.py文件,在文件中对login.json文件内容进行读取操作,代码如下

5.重构json工具类

  • 将上一步操作json的代码进行封装
 
  1. class operationJson(object):

  2. def __init__(self, file_path="../test_data/login.json"):

  3. self.file_path = file_path

  4. self.data = self.get_data()

  5. def get_data(self):

  6. with open(self.file_path) as f:

  7. data = json.load(f)

  8. return data

  9. def get_key_words(self, key=None):

  10. if key:

  11. return self.data[key]

  12. else:

  13. return self.data

  14. if __name__ == '__main__':

  15. print(operationJson().get_key_words())

  16. print(operationJson().get_key_words("login"))

  17. print(operationJson().get_key_words("login")['username'])

  • 运行op_json.py文件,结果与封装之前代码结果一致,表示重构封装代码成功

6.封装获取常量方法

  • 首先打开excel表格,查看需要获取的字段有哪些

  • 对excel表的字段进行获取,在项目目录下创建名为data的python包,在该包下创建data_conf.py,代码就是简单的获取对应的变量值,具体如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/21 9:29'

  4. class global_var:

  5. id = '0' # id

  6. module = '1' # 模块

  7. url = '2' # url

  8. run = '3' # 是否运行

  9. request_type = '4' # 请求类型

  10. request_header = '5' # 是否携带header

  11. case_depend = '6' # case依赖

  12. response_data_depend = '7' # 依赖的返回数据

  13. data_depend = '8' # 数据依赖

  14. request_data = '9' # 请求数据

  15. expect_result = '10' # 预期结果

  16. reality_result = '11' # 实际结果

  17. def get_id():

  18. return global_var.id

  19. def get_module():

  20. return global_var.module

  21. def get_url():

  22. return global_var.url

  23. def get_run():

  24. return global_var.run

  25. def get_request_type():

  26. return global_var.request_type

  27. def get_request_header():

  28. return global_var.request_header

  29. def get_case_depend():

  30. return global_var.case_depend

  31. def get_response_data_depend():

  32. return global_var.response_data_depend

  33. def get_data_depend():

  34. return global_var.data_depend

  35. def get_request_data():

  36. return global_var.request_data

  37. def get_expect_result():

  38. return global_var.expect_result

  39. def get_reality_result():

  40. return global_var.reality_result

7.封装获取接口数据

  • 在data目录下创建data_get.py文件,在该文件中对excel表数据以及json数据结合上一步封装的常量方法整合后的实现,代码如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/21 10:01'

  4. from utils.op_excel import operationExcel

  5. from utils.op_json import operationJson

  6. from data import data_conf

  7. class getData(object):

  8. def __init__(self):

  9. self.op_excel = operationExcel()

  10. def get_case_lines(self):

  11. """获取表格行数"""

  12. return self.op_excel.get_rows()

  13. def get_is_run(self, x):

  14. """获取case是否运行"""

  15. flag = None

  16. y = data_conf.get_run()

  17. run_value = self.op_excel.get_cell_value(x, y)

  18. if run_value == 'yes':

  19. flag = True

  20. else:

  21. flag = False

  22. return flag

  23. def get_is_header(self, x):

  24. """是否携带header"""

  25. y = data_conf.get_request_header()

  26. header = self.op_excel.get_cell_value(x, y)

  27. if header == 'yes':

  28. return data_conf.get_header_value()

  29. else:

  30. return None

  31. def get_request_method(self, x):

  32. """获取请求方式"""

  33. y = data_conf.get_request_type()

  34. request_method = self.op_excel.get_cell_value(x, y)

  35. return request_method

  36. def get_request_url(self, x):

  37. """获取请求地址"""

  38. y = data_conf.get_url()

  39. request_url = self.op_excel.get_cell_value(x, y)

  40. return request_url

  41. def get_request_data(self, x):

  42. """获取请求数据"""

  43. y = data_conf.get_request_data()

  44. request_data = self.op_excel.get_cell_value(x, y)

  45. if request_data == '':

  46. return None

  47. return request_data

  48. def get_data_for_json(self, x):

  49. """通过excel中的关键字去获取json数据"""

  50. op_json = operationJson()

  51. data = op_json.get_key_words(self.get_request_data(x))

  52. return data

  53. def get_expect_data(self, x):

  54. """获取预期结果数据"""

  55. y = data_conf.get_expect_result()

  56. expect_data = self.op_excel.get_cell_value(x, y)

  57. if expect_data == '':

  58. return None

  59. return expect_data

8.post、get基类的封装

  • 在base包下创建run_method.py文件,在文件中重新编写对get丶post请求方式的代码封装,具体如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/21 11:19'

  4. import requests

  5. class RunMain(object):

  6. def get_main(self, url, data=None, header=None):

  7. res = None

  8. if header is not None:

  9. res = requests.get(url=url, data=data, headers=header).json()

  10. else:

  11. res = requests.get(url=url, data=data).json()

  12. return res

  13. def post_main(self, url, data, header=None):

  14. res = None

  15. if header is not None:

  16. res = requests.post(url=url, data=data, headers=header).json()

  17. else:

  18. res = requests.post(url=url, data=data).json()

  19. return res

  20. def run_main(self, url, method, data=None, header=None):

  21. res = None

  22. if method.lower() == 'post':

  23. res = self.post_main(url, data, header)

  24. elif method.lower() == 'get':

  25. res = self.get_main(url, data, header)

  26. else:

  27. return "what ?????"

  28. return res

9.主流程封装及错误解决调试

  • 首先在testItems项目目录下新建一个名为main的python包,在该包下创建名为run_test的py文件,该文件为主程序启动文件,代码的逻辑就是将前面封装的方法进行了调用核心就是读取excel表的数据,通过读取到的数据,发送请求,其中包括某一些变量的判断,根据该判断然后到json数据中获取请求的数据,最后就这么的简单,代码如下
 
  1. # -*- coding: utf-8 -*-

  2. __author__ = 'cdtaogang'

  3. __date__ = '2019/6/21 11:57'

  4. from base.run_method import RunMain

  5. from data.data_get import getData

  6. class RunTest(object):

  7. def __init__(self):

  8. self.runmain = RunMain()

  9. self.data = getData()

  10. def run(self):

  11. res = None

  12. row_counts = self.data.get_case_lines() # 获取excel表格行数

  13. # print(row_counts) 5

  14. for row_count in range(1, row_counts):

  15. # print(row_count) 1,2,3,4

  16. url = self.data.get_request_url(row_count) # y行不变遍历获取x列的请求地址

  17. method = self.data.get_request_method(row_count) # y行不变遍历获取x列的请求方式

  18. is_run = self.data.get_is_run(row_count) # y行不变遍历获取x列的是否运行

  19. data = self.data.get_data_for_json(row_count) # y行不变遍历获取x列的请求数据,这里面时三次调用,依次分别是get_data_for_json丶get_key_words丶get_request_data

  20. header = self.data.get_is_header(row_count)

  21. print('url:', url)

  22. print('method:', method)

  23. print('is_run:', is_run)

  24. print('data:', data)

  25. print('header:', header)

  26. if is_run:

  27. res = self.runmain.run_main(url,method,data,header)

  28. print("*"*60+"分割线"+"*"*60)

  29. return res

  30. if __name__ == '__main__':

  31. print('res:', RunTest().run())

运行run_test,成功的将excel以及json数据正确打印出来,返回res服务器返回结果,需要说明的是excel表中的所有数据都不是真实存在的,包括json文档数据也是,这里主要是测试整个框架的正确性读取excel以及json文档数据,并正确的发送请求获得相应数据

  • 运行结果出现红色的内容,是由requests模块发送请求的安全请求警告,如不想显示此警告,可以在run_method.py发送请求核心代码进行禁用,禁用代码如下

  • 重新运行run_test,安全请求警告不再显示

  • 根据代码运行结果,对比excel表以及json数据文档内容,数据正确无误

10. 返回数据格式处理以及调错

  • 为了测试返回的接口的响应数据,博主这里在excel文档以及json文档中添加了一条数据

  • 因为在excel文档中小说的接口不携带header所以在向接口发送请求数据核心代码块,进行了如下修改,因为在excel文档中的最后一个接口时真实的,所以只需要对最后一个接口url返回的字典类型的响应数据进行转换成json格式的数据,并按照关键字进行排序

  • 运行run_test,在最后一个接口中成功打印出我们想要的数据
总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

  视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取


http://www.ppmy.cn/news/1340551.html

相关文章

把 matlab 公式输出成 latex 公式形式

问题 latex 进行符号计算后,想直接把 matlab 中变量代表的公式结果输出成 latex 形式。 这样可以直接 复制到 latex 中,不需要手打公式了。 方法 matlab 函数 latex 可以实现上述功能,但最好是 使用 simpify(expand(~)) 进行化简 str_Jac…

Acwing---802.区间和

区间和 1.题目2.基本思想3.代码实现 1.题目 假定有一个无限长的数轴,数轴上每个坐标上的数都是 0。 现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c。 接下来,进行 m次询问,每个询问包含两个整数 l 和…

Android悬浮窗实现步骤

最近想做一个悬浮窗秒表的功能,所以看下悬浮窗具体的实现步骤 1、初识WindowManager 实现悬浮窗主要用到的是WindowManager SystemService(Context.WINDOW_SERVICE) public interface WindowManager extends ViewManager {... }WindowManager是接口类&#xff0c…

【NLP冲吖~】一、朴素贝叶斯(Naive Bayes)

0、朴素贝叶斯法 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布,然后基于此模型,对给定的输入 x x x,利用贝叶斯定理求出后验概率最大的…

如何在Shopee平台上进行手机类目选品?

在Shopee平台上进行手机类目的选品是一个关键而复杂的任务。卖家需要经过一系列的策略和步骤,以确保选品的成功和销售业绩的提升。下面将介绍一些有效的策略,帮助卖家在Shopee平台上进行手机类目选品。 先给大家推荐一款shopee知虾数据运营工具知虾免费…

C# 引用同一个dll不同版本的程序集

因为项目需要所以必须在项目中引用不同版本的同一程序集 我要引用的文件是newtonsoft.json.dll 两个版本为12.0.0.0 和4.0.0.0 1.如果已经先引入了newtonsoft.json 12.0.0.0版本的程序集,如果直接引入另一个版本的程序集的话会提示不成功,所以先将另一个…

c++ 语法指针

指针 1.指针就是一个地址 2. 指针本身也是有地址的 3.取指针所指向的地址保存的值 用变量名取 4.取指针所指向地址保存的值 *变量名取&#xff08;解引用&#xff09; int main(int argc, const char * argv[]) {// insert code here...std::cout << "Hello, …

Leetcode—2670. 找出不同元素数目差数组【简单】

2024每日刷题&#xff08;一零七&#xff09; Leetcode—2670. 找出不同元素数目差数组 哈希表实现代码 class Solution { public:vector<int> distinctDifferenceArray(vector<int>& nums) {unordered_set<int> s;int n nums.size();vector<int&g…

openstack

在虚拟机上安装完openstack之后&#xff0c;根据你想提供的服务&#xff0c;再去安装一些组件(服务)&#xff0c;比如说 (Nova)&#xff1a;用于虚拟机的管理、调度和协调。 (Neutron)&#xff1a;用于管理虚拟网络和网络服务。 (Cinder)&#xff1a;提供块级存储服务&#…

vue 发布自己的npm组件

1、在项目任意位置创建index.ts文件 2、导入要到处的组件&#xff0c;使用vue提供的install 功能全局挂在&#xff1b; import GWButton from "/views/GWButton.vue"; import GWAbout from "/views/AboutView.vue";const components {GWButton,GWAbout, …

C++学习Day01之namespace命名空间

目录 一、程序及输出1.1 命名空间用途&#xff1a; 解决名称冲突1.2 命名空间内容1.3 命名空间必须要声明在全局作用域下1.4 命名空间可以嵌套命名空间1.5 命名空间开放&#xff0c;可以随时给命名空间添加新的成员1.6 命名空间可以是匿名的1.7 命名空间可以起别名 二、分析与总…

MySQL EXPLAIN查询执行计划

EXPLAIN 可用来查看SQL执行计划&#xff0c;常用来分析调试SQL语句&#xff0c;来使SQL语句达到更好的性能。 1 前置知识 在学习EXPLAIN 之前&#xff0c;有些基础知识需要清楚。 1.1 JSON类型 MySQL 5.7及以上版本支持JSON数据类型。可以将数组存为JSON格式的字符串&#…

unity3d的海盗王白银城演示

这是一个外网上的下载的海盗王unity3d制作的白银城演示场景。 地图只含有白银城区&#xff0c;没有野外和怪物。 当然也没有服务器端的。 我对灯光、摄像头、天空背景等做过调整&#xff0c;使它显示起来比较鲜丽。 它的模型和贴图是直接拿了海盗的&#xff0c;没有做过优化调整…

Spring Cloud Gateway 修改请求体、响应体

前言 例行每半年一次的工作轮换&#xff0c;接手了同事的网关服务 年底了工作不是很忙&#xff0c;看了下前人的代码&#xff0c;虽然都能读懂&#xff0c;但感觉应该可以再优雅一点 于是把网关的相关知识又翻阅了一下 官方资料 PS&#xff1a;这里如果按新方案调整的话&#…

2024美赛数学建模F题思路分析 - 减少非法野生动物贸易

1 赛题 问题F&#xff1a;减少非法野生动物贸易 非法的野生动物贸易会对我们的环境产生负面影响&#xff0c;并威胁到全球的生物多样性。据估计&#xff0c;它每年涉及高达265亿美元&#xff0c;被认为是全球第四大非法交易。[1]你将开发一个由数据驱动的5年项目&#xff0c;…

双非本科准备秋招(14.1)—— 力扣刷题

今天做两个有点难度的题。 1、295. 数据流的中位数 手写堆实现&#xff1a; 加入元素&#xff1a; 如何维护一个中位数&#xff1f;我们考虑一下堆的特点&#xff0c;大顶堆堆顶是一个最大值&#xff0c;小顶堆堆顶是一个最小值&#xff0c;那么&#xff0c;如果我们可以把数…

jenkins 下载插件sentry-cli失败 证书过期

现状 npm set ENTRYCLI_CDNURLhttps://cdn.npm.taobao.org/dist/sentry-cli npm set sentrycli_cdnurlhttps://cdn.npm.taobao.org/dist/sentry-cli 原因是npm原域名停止解析&#xff0c;在访问上面sentry-cli的cdn资源的时候 证书过期无法下载。 解决&#xff1a; 替换证书过期…

代码随想录算法训练营29期Day37|LeetCode 738,968

文档讲解&#xff1a;单调递增的数字 监控二叉树 贪心算法总结 738.单调递增的数字 题目链接&#xff1a;https://leetcode.cn/problems/monotone-increasing-digits/description/ 思路&#xff1a; 题目要求小于等于N的最大单调递增的整数&#xff0c;那么拿一个两位的数字…

element-ui icon 组件源码分享

今日简单分享 element-ui 源码中的 icon 组件&#xff0c;主要从以下两个方面来分享&#xff1a; 一、源码中 icon 设计思想是什么呢&#xff1f;主要从页面结构、数据、 icon 样式三个方面来分享。 1.1 源码中 icon 组件的页面结构&#xff0c;可以在 package 目录下找到 ico…

【Node系列】REPL详解

文章目录 一、REPL介绍二、REPL案例三、REPL命令四、node介绍五、相关链接 一、REPL介绍 Node.js REPL&#xff08;Read-Eval-Print Loop&#xff09;是一个交互式环境&#xff0c;允许用户在命令行中直接输入JavaScript代码并立即看到结果。REPL是Node.js的一个重要组成部分&…
最新文章