测试开发进阶(三十八)
- 2019 年 11 月 25 日
- 笔记
用例模块
需要使用httprunner来进行用例的执行与报告的生成
所以我们需要生成一个yaml用例文件,再执行它
@action(methods=['post'], detail=True) def run(self, request, *args, **kwargs): instance = self.get_object() serializer = self.get_serializer(instance, data=request.data) serializer.is_valid(raise_exception=True) datas = serializer.validated_data env_id = datas.get('env_id') testcase_dir_path = os.path.join(settings.SUITES_DIR, datetime.strftime(datetime.now(), '%Y%m%d%H%M%S%f')) if not os.path.exists(testcase_dir_path): os.mkdir(testcase_dir_path) env = Envs.objects.filter(id=env_id, is_delete=False).first() # 生成yaml用例文件 common.generate_testcase_files(instance, env, testcase_dir_path) # 运行用例 return common.run_testcase(instance, testcase_dir_path)
生成yaml用例
官方文档有一个例子:
- config: name: testcase description variables: {} - test: name: /api/get-token request: headers: Content-Type: application/json User-Agent: python-requests/2.18.4 app_version: 2.8.6 device_sn: FwgRiO7CNA50DSU os_platform: ios json: sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98 method: POST url: http://127.0.0.1:5000/api/get-token extract: token: content.token validate: - eq: [status_code, 200] - eq: [headers.Content-Type, application/json] - eq: [content.success, true] - test: name: /api/users/1000 request: headers: Content-Type: application/json User-Agent: python-requests/2.18.4 device_sn: FwgRiO7CNA50DSU token: $token json: name: user1 password: '123456' method: POST url: http://127.0.0.1:5000/api/users/1000 validate: - eq: [status_code, 201] - eq: [headers.Content-Type, application/json] - eq: [content.success, true] - eq: [content.msg, user created successfully.]
上述一个yaml对应的json格式为:
[ { "config": { "name": "testcase description", "request": { "base_url": "", "headers": { "User-Agent": "python-requests/2.18.4" } }, "variables": [], "output": ["token"], "path": "/abs-path/to/demo-quickstart-2.yml", "refs": { "env": {}, "debugtalk": { "variables": { "SECRET_KEY": "DebugTalk" }, "functions": { "gen_random_string": <function gen_random_string at 0x108596268>, "get_sign": <function get_sign at 0x1085962f0>, "get_user_id": <function get_user_id at 0x108596378>, "get_account": <function get_account at 0x108596400>, "get_os_platform": <function get_os_platform at 0x108596488> } }, "def-api": {}, "def-testcase": {} } }, "teststeps": [ { "name": "/api/get-token", "request": { "url": "http://127.0.0.1:5000/api/get-token", "method": "POST", "headers": {"Content-Type": "application/json", "app_version": "2.8.6", "device_sn": "FwgRiO7CNA50DSU", "os_platform": "ios", "user_agent": "iOS/10.3"}, "json": {"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"} }, "extract": [ {"token": "content.token"} ], "validate": [ {"eq": ["status_code", 200]}, {"eq": ["headers.Content-Type", "application/json"]}, {"eq": ["content.success", true]} ] }, { "name": "/api/users/1000", "request": {"url": "http://127.0.0.1:5000/api/users/1000", "method": "POST", "headers": {"Content-Type": "application/json", "device_sn": "FwgRiO7CNA50DSU", "token": "$token"}, "json": {"name": "user1", "password": "123456"}}, "validate": [ {"eq": ["status_code", 201]}, {"eq": ["headers.Content-Type", "application/json"]}, {"eq": ["content.success", true]}, {"eq": ["content.msg", "user created successfully."]} ] } ] }, {...} # another testcase ]
所以我们需要通过一个函数将已有的接口,环境,配置写入一份yaml中
将对应的 debugtalk.py
存放在yaml文件附近
def generate_testcase_files(instance, env, testcase_dir_path): testcase_list = [] config = { 'config': { 'name': instance.name, 'request': { 'base_url': env.base_url if env else '' } } } testcase_list.append(config) include = json.loads(instance.include, encoding='utf8') request = json.loads(instance.request, encoding='utf8') interface_name = instance.interface.name project_name = instance.interface.project.name testcase_dir_path = os.path.join(testcase_dir_path, project_name) if not os.path.exists(testcase_dir_path): os.mkdir(testcase_dir_path) debugtalk_obj = DebugTalks.objects.filter(is_delete=False, project__name=project_name).first() if debugtalk_obj: debugtalk = debugtalk_obj.debugtalk else: debugtalk = '' with open(os.path.join(testcase_dir_path, 'debugtalk.py'), mode='w', encoding='utf8') as one_file: one_file.write(debugtalk) testcase_dir_path = os.path.join(testcase_dir_path, interface_name) if not os.path.exists(testcase_dir_path): os.mkdir(testcase_dir_path) if 'config' in include: config_id = include.get('config') config_obj = Configures.objects.filter(is_delete=False, id=config_id).first() if config_obj: config_request = json.loads(config_obj.request, encoding='utf8') config_request.get('config').get('request').setdefault('base_url', env.base_url) config_request['config']['name'] = instance.name testcase_list[0] = config_request if 'testcases' in include: for t_id in include.get('testcases'): testcase_obj = Testcases.objects.filter(is_delete=False, id=t_id).first() if testcase_obj: try: testcase_request = json.loads(testcase_obj.request, encoding='utf8') except Exception as e: testcase_request = '' else: testcase_list.append(testcase_request) testcase_list.append(request) with open(os.path.join(testcase_dir_path, instance.name + '.yml'), 'w', encoding='utf8') as one_file: yaml.dump(testcase_list, one_file, allow_unicode=True)
执行yaml文件

从https://cn.httprunner.org/development/dev-api/可以看出,我们可以通过传入yaml路径来执行测试
def run_testcase(instance, testcase_dir_path): """ 运行用例 :return: :param instance: 实例 :param testcase_dir_path: 用例根目录路径 :return dict """ runner = HttpRunner() # runner.run(testcase_dir_path) try: runner.run(testcase_dir_path) except ParamsError: logger.error("用例参数有误") data = { "msg": "用例参数有误" } return Response(data, status=400) runner.summary = timestamp_to_datetime(runner.summary, type=False) try: report_name = instance.name except Exception as e: report_name = '被遗弃的报告' + '-' + datetime.strftime(datetime.now(), '%Y%m%d%H%M%S') report_id = create_report(runner, report_name=report_name) data_dict = { "id": report_id } return Response(data_dict, status=status.HTTP_201_CREATED)
报告中的时间格式需要进行调整
def create_report(runner, report_name=None): """ 创建测试报告 :param runner: :param report_name: :return: """ time_stamp = int(runner.summary["time"]["start_at"]) start_datetime = datetime.fromtimestamp(time_stamp).strftime('%Y-%m-%d %H:%M:%S') runner.summary['time']['start_datetime'] = start_datetime # duration保留3位小数 runner.summary['time']['duration'] = round(runner.summary['time']['duration'], 3) report_name = report_name if report_name else start_datetime runner.summary['html_report_name'] = report_name for item in runner.summary['details']: try: for record in item['records']: record['meta_data']['response']['content'] = record['meta_data']['response']['content']. decode('utf-8') record['meta_data']['response']['cookies'] = dict(record['meta_data']['response']['cookies']) request_body = record['meta_data']['request']['body'] if isinstance(request_body, bytes): record['meta_data']['request']['body'] = request_body.decode('utf-8') except Exception as e: continue summary = json.dumps(runner.summary, ensure_ascii=False) report_name = report_name + '_' + datetime.strftime(datetime.now(), '%Y%m%d%H%M%S') report_path = runner.gen_html_report(html_report_name=report_name) with open(report_path, encoding='utf-8') as stream: reports = stream.read() test_report = { 'name': report_name, 'result': runner.summary.get('success'), 'success': runner.summary.get('stat').get('successes'), 'count': runner.summary.get('stat').get('testsRun'), 'html': reports, 'summary': summary } report_obj = Reports.objects.create(**test_report) return report_obj.id

其他模块的执行与报告展示也调用了这两个函数
https://github.com/zx490336534/ApiTest