若有任何学习问题,能够添加做者微信:lockingfreehtml
Python接口测试实战1(上)- 接口测试理论
Python接口测试实战1(下)- 接口测试工具的使用
Python接口测试实战2 - 使用Python发送请求
Python接口测试实战3(上)- Python操做数据库
Python接口测试实战3(下)- unittest测试框架
Python接口测试实战4(上) - 接口测试框架实战
Python接口测试实战4(下) - 框架完善:用例基类,用例标签,从新运行上次失败用例
Python接口测试实战5(上) - Git及Jenkins持续集成
Python接口测试实战5(下) - RESTful、Web Service及Mock Serverpython
更多学习资料请加QQ群: 822601020获取数据库
本节内容json
由于每条用例都须要从excel中读取数据,解析数据,发送请求,断言响应结果,咱们能够封装一个BaseCase
的用例基础类,对一些方法进行封装,来简化用例编写api
从新规划了test目录,在test下创建case文件夹存放用例,创建suite文件夹存放自定义的
TestSuite
test_user_data.xlsx
中增长了一列data_type
,FORM
指表单格式请求,JSON
指JSON格式请求微信
项目test/case文件夹下新建basecase.py网络
import unittest import requests import json import sys sys.path.append("../..") # 统一将包的搜索路径提高到项目根目录下 from lib.read_excel import * from lib.case_log import log_case_info class BaseCase(unittest.TestCase): # 继承unittest.TestCase @classmethod def setUpClass(cls): if cls.__name__ != 'BaseCase': cls.data_list = excel_to_list(data_file, cls.__name__) def get_case_data(self, case_name): return get_test_data(self.data_list, case_name) def send_request(self, case_data): case_name = case_data.get('case_name') url = case_data.get('url') args = case_data.get('args') headers = case_data.get('headers') expect_res = case_data.get('expect_res') method = case_data.get('method') data_type = case_data.get('data_type') if method.upper() == 'GET': # GET类型请求 res = requests.get(url=url, params=json.loads(args)) elif data_type.upper() == 'FORM': # 表单格式请求 res = requests.post(url=url, data=json.loads(args), headers=json.loads(headers)) log_case_info(case_name, url, args, expect_res, res.text) self.assertEqual(res.text, expect_res) else: res = requests.post(url=url, json=json.loads(args), headers=json.loads(headers)) # JSON格式请求 log_case_info(case_name, url, args, json.dumps(json.loads(expect_res), sort_keys=True), json.dumps(res.json(), ensure_ascii=False, sort_keys=True)) self.assertDictEqual(res.json(), json.loads(expect_res))
简化后的用例:
test/case/user/test_user_login.py
app
from test.case.basecase import BaseCase class TestUserLogin(BaseCase): # 这里直接继承BaseCase def test_user_login_normal(self): """level1:正常登陆""" case_data = self.get_case_data("test_user_login_normal") self.send_request(case_data) def test_user_login_password_wrong(self): """密码错误登陆""" case_data = self.get_case_data("test_user_login_password_wrong") self.send_request(case_data)
test/case/user/test_user_reg.py
框架
from test.case.basecase import BaseCase from lib.db import * import json class TestUserReg(BaseCase): def test_user_reg_normal(self): case_data = self.get_case_data("test_user_reg_normal") # 环境检查 name = json.loads(case_data.get("args")).get('name') # 范冰冰 if check_user(name): del_user(name) # 发送请求 self.send_request(case_data) # 数据库断言 self.assertTrue(check_user(name)) # 环境清理 del_user(name) def test_user_reg_exist(self): case_data = self.get_case_data("test_user_reg_exist") name = json.loads(case_data.get("args")).get('name') # 环境检查 if not check_user(name): add_user(name, '123456') # 发送请求 self.send_request(case_data)
以前咱们的run_all.py
只有运行全部用例一种选择,咱们经过增长一些功能,提供更灵活的运行策略工具
TestSuite
项目test/suite文件夹下新建test_suites.py
import unittest import sys sys.path.append("../..") from test.case.user.test_user_login import TestUserLogin from test.case.user.test_user_reg import TestUserReg smoke_suite = unittest.TestSuite() # 自定义的TestSuite smoke_suite.addTests([TestUserLogin('test_user_login_normal'), TestUserReg('test_user_reg_normal')]) def get_suite(suite_name): # 获取TestSuite方法 return globals().get(suite_name)
修改run_all.py
为run.py
,添加run_suite()
方法
import unittest from lib.HTMLTestReportCN import HTMLTestRunner from config.config import * from lib.send_email import send_email from test.suite.test_suites import * def discover(): return unittest.defaultTestLoader.discover(test_case_path) def run(suite): logging.info("================================== 测试开始 ==================================") with open(report_file, 'wb') as f: HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite) # send_email(report_file) logging.info("================================== 测试结束 ==================================") def run_all(): # 运行所用用例 run(discover()) def run_suite(suite_name): # 运行`test/suite/test_suites.py`文件中自定义的TestSuite suite = get_suite(suite_name) if suite: run(suite) else: print("TestSuite不存在")
run.py
中添加
def collect(): # 因为使用discover() 组装的TestSuite是按文件夹目录多级嵌套的,咱们把全部用例取出,放到一个无嵌套的TestSuite中,方便以后操做 suite = unittest.TestSuite() def _collect(tests): # 递归,若是下级元素仍是TestSuite则继续往下找 if isinstance(tests, unittest.TestSuite): if tests.countTestCases() != 0: for i in tests: _collect(i) else: suite.addTest(tests) # 若是下级元素是TestCase,则添加到TestSuite中 _collect(discover()) return suite def collect_only(): # 仅列出所用用例 t0 = time.time() i = 0 for case in collect(): i += 1 print("{}.{}".format(str(i), case.id())) print("----------------------------------------------------------------------") print("Collect {} tests is {:.3f}s".format(str(i),time.time()-t0))
test文件夹下新建testlist.txt
,内容以下
test_user_login_normal test_user_reg_normal # test_user_reg_exist # 注释后不执行
run.py
中添加
def makesuite_by_testlist(testlist_file): # test_list_file配置在config/config.py中 with open(testlist_file) as f: testlist = f.readlines() testlist = [i.strip() for i in testlist if not i.startswith("#")] # 去掉每行结尾的"/n"和 #号开头的行 suite = unittest.TestSuite() all_cases = collect() # 全部用例 for case in all_cases: # 从全部用例中匹配用例方法名 if case._testMethodName in testlist: suite.addTest(case) return suite
因为TestSuite咱们必须提早组装好,而为每一个用例方法添加上标签,而后运行指定标签的用例能更加灵活
遗憾的是,unittest并无tag相关功能,一种实现方案是:
def tag(tag): if tag==OptionParser.options.tag: # 运行的命令行参数 return lambda func: func # 若是用例的tag==命令行指定的tag参数,返回用例自己 return unittest.skip("跳过不包含该tag的用例") # 不然跳过用例
用例标记方法
@tag("level1") def test_a(self): pass
这种方法在最后的报告中会出现不少skipped
的用例,可能会干扰到因其余(如环境)缘由须要跳过的用例
我这里的实现方法是经过判断用例方法中的docstring中加入特定的标签来从新组织TestSuite的方式
run.py
中添加
def makesuite_by_tag(tag): suite = unittest.TestSuite() for case in collect(): if case._testMethodDoc and tag in case._testMethodDoc: # 若是用例方法存在docstring,而且docstring中包含本标签 suite.addTest(case) return suite
用例标记方法
class TestUserLogin(BaseCase): def test_user_login_normal(self): """level1:正常登陆""" # level1及是一个标签,放到docstring哪里均可以 case_data = self.get_case_data("test_user_login_normal") self.send_request(case_data)
咱们在每次执行后,经过执行结果result.failures获取到失败的用例,组装成TestSuite并序列化到指定文件中,rerun-fails时,反序列化获得上次执行失败的TestSuite, 而后运行
在run.py
中添加
import pickle import sys def save_failures(result, file): # file为序列化保存的文件名,配置在config/config.py中 suite = unittest.TestSuite() for case_result in result.failures: # 组装TestSuite suite.addTest(case_result[0]) # case_result是个元祖,第一个元素是用例对象,后面是失败缘由等等 with open(file, 'wb') as f: pickle.dump(suite, f) # 序列化到指定文件 def rerun_fails(): # 失败用例重跑方法 sys.path.append(test_case_path) # 须要将用例路径添加到包搜索路径中,否则反序列化TestSuite会找不到用例 with open(last_fails_file, 'rb') as f: suite = pickle.load(f) # 反序列化获得TestSuite run(suite)
修改run.py
中的run()方法,运行后保存失败用例序列化文件
def run(suite): logging.info("================================== 测试开始 ==================================") with open(report_file, 'wb') as f: # 结果赋予result变量 result = HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite) if result.failures: # 保存失败用例序列化文件 save_failures(result, last_fails_file) # send_email(report_file) # 从配置文件中读取 logging.info("================================== 测试结束 ==================================")
命令行参数是咱们经过命令行调用run.py
(执行入口文件)传递的一些参数,经过不一样的参数,执行不一样的运行策略,如python run.py --collect-only
咱们经过optparser实现命令行参数:
在config/config.py
中添加
# 命令行选项 parser = OptionParser() parser.add_option('--collect-only', action='store_true', dest='collect_only', help='仅列出全部用例') parser.add_option('--rerun-fails', action='store_true', dest='rerun_fails', help='运行上次失败的用例') parser.add_option('--testlist', action='store_true', dest='testlist', help='运行test/testlist.txt列表指定用例') parser.add_option('--testsuite', action='store', dest='testsuite', help='运行指定的TestSuite') parser.add_option('--tag', action='store', dest='tag', help='运行指定tag的用例') (options, args) = parser.parse_args() # 应用选项(使生效)
命令行选项使用方法:
run.py
中添加:
from config.config import * def main(): if options.collect_only: # 若是指定了--collect-only参数 collect_only() elif options.rerun_fails: # 若是指定了--rerun-fails参数 rerun_fails() elif options.testlist: # 若是指定了--testlist参数 run(makesuite_by_testlist(testlist_file)) elif options.testsuite: # 若是指定了--testsuite=*** run_suite(options.testsuite) elif options.tag: # 若是指定了--tag=*** run(makesuite_by_tag(options.tag)) else: # 不然,运行全部用例 run_all() if __name__ == '__main__': main() # 调用main()
运行结果:
C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --collect-only 1.user.test_user_login.TestUserLogin.test_user_login_normal 2.user.test_user_login.TestUserLogin.test_user_login_password_wrong 3.user.test_user_reg.TestUserReg.test_user_reg_exist 4.user.test_user_reg.TestUserReg.test_user_reg_normal ---------------------------------------------------------------------- Collect 4 tests is 0.006s C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --rerun-fails . Time Elapsed: 0:00:00.081812 C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testlist .. Time Elapsed: 0:00:00.454654 C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --testsuite=smoke_suite .. Time Elapsed: 0:00:00.471255 C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py --tag=level1 . Time Elapsed: 0:00:00.062273 C:\Users\hanzhichao\PycharmProjects\api_test_framework_finish>python run.py .... Time Elapsed: 0:00:00.663564
1.按天生成log,每次执行生成新的报告
修改config/config.py
import time today = time.strftime('%Y%m%d', time.localtime()) now = time.strftime('%Y%m%d_%H%M%S', time.localtime()) log_file = os.path.join(prj_path, 'log', 'log_{}.txt'.format(today)) # 更改路径到log目录下 report_file = os.path.join(prj_path, 'report', 'report_{}.html'.format(now)) # 更改路径到report目录下
2.增长send_email()开关
config/config.py
增长
send_email_after_run = False
修改run.py
from config.config import * def run(suite): logging.info("================================== 测试开始 ==================================") with open(report_file, 'wb') as f: # 从配置文件中读取 result = HTMLTestRunner(stream=f, title="Api Test", description="测试描述", tester="卡卡").run(suite) if result.failures: save_failures(result, last_fails_file) if send_email_after_run: # 是否发送邮件 send_email(report_file) logging.info("================================== 测试结束 ==================================")
发送最新报告的问题稍后解决
源码地址: 连接:https://pan.baidu.com/s/1DLNSKN0KKuvSgo7gbGbMeg 密码:994e
此为北京龙腾育才 Python高级自动化(接口测试部分)授课笔记
课程介绍
想要参加现场(北京)/网络课程的能够联系做者微信:lockingfree
- 高效学习,快速掌握Python自动化全部领域技能
- 同步快速解决各类问题
- 配套实战项目练习