使用 HttpRunner 默認生成的項目是這樣的
命令:httprunner --startproject 項目名稱
so,根據這個項目的目錄結構,使用python解析swagger接口參數,可以快速生成api、testcases、testsuites文件夾中用到的json文件
運行后的目錄是這樣的
api目錄
按swagger中的tags區分為多個文件夾,每個文件夾下包含各自的api文件
testcases目錄
按swagger中的tags區分為不同的json文件,每個文件包含所有的api接口
testsuites目錄
測試用例集,組織運行所有的測試用例
這樣,接口測試框架的簡易架子就有了。接下來,需要補充api接口文件數據
略
運行結果
HttpRunner自帶的report
附lib目錄下的代碼
swagger.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/9/9 15:17 # @Author : lixiaofeng # @Site : # @File : swagger.py # @Software: PyCharm import os, requests from httprunner import logger from lib.processingJson import write_data, get_json class AnalysisJson: """swagger自動生成測試用例""" def __init__(self, url): self.url = url self.interface = {} self.case_list = [] self.tags_list = [] self.http_suite = {"config": {"name": "", "base_url": "", "variables": {}}, "testcases": []} self.http_testcase = {"name": "", "testcase": "", "variables": {}} def retrieve_data(self): """ 主函數 :return: """ try: r = requests.get(self.url + '/v2/api-docs?group=sign-api').json() write_data(r, 'data.json') # r = get_json('D:\HttpRunner_framework\\testcases\data.json') except Exception as e: logger.log_error('請求swagger url 發生錯誤. 詳情原因: {}'.format(e)) return 'error' self.data = r['paths'] # 接口數據 self.url = 'https://' + r['host'] self.title = r['info']['title'] self.http_suite['config']['name'] = self.title self.http_suite['config']['base_url'] = self.url self.definitions = r['definitions'] # body參數 for tag_dict in r['tags']: self.tags_list.append(tag_dict['name']) i = 0 for tag in self.tags_list: self.http_suite['testcases'].append({"name": "", "testcase": "", "variables": {}}) self.http_suite['testcases'][i]['name'] = tag self.http_suite['testcases'][i]['testcase'] = 'testcases/' + tag + '.json' i += 1 suite_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname("__file__"), os.path.pardir)), 'testsuites') testcase_path = os.path.join(suite_path, 'demo_testsuite.json') write_data(self.http_suite, testcase_path) if isinstance(self.data, dict): for tag in self.tags_list: self.http_case = {"config": {"name": "", "base_url": "", "variables": {}}, "teststeps": []} for key, value in self.data.items(): for method in list(value.keys()): params = value[method] if not params['deprecated']: # 接口是否被棄用 if params['tags'][0] == tag: self.http_case['config']['name'] = params['tags'][0] self.http_case['config']['base_url'] = self.url case = self.retrieve_params(params, key, method, tag) self.http_case['teststeps'].append(case) else: logger.log_info( 'interface path: {}, if name: {}, is deprecated.'.format(key, params['description'])) break api_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname("__file__"), os.path.pardir)), 'testcases') testcase_path = os.path.join(api_path, tag + '.json') write_data(self.http_case, testcase_path) else: logger.log_error('解析接口數據異常!url 返回值 paths 中不是字典.') return 'error' def retrieve_params(self, params, api, method, tag): """ 解析json,把每個接口數據都加入到一個字典中 :param params: :param params_key: :param method: :param key: :return: replace('false', 'False').replace('true', 'True').replace('null','None') """ http_interface = {"name": "", "variables": {}, "request": {"url": "", "method": "", "headers": {}, "json": {}, "params": {}}, "validate": [], "output": []} http_testcase = {"name": "", "api": "", "variables": {}, "validate": [], "extract": [], "output": []} name = params['summary'].replace('/', '_') http_interface['name'] = name http_testcase['name'] = name http_testcase['api'] = 'api/{}/{}.json'.format(tag, name) http_interface['request']['method'] = method.upper() http_interface['request']['url'] = api.replace('{', '$').replace('}', '') parameters = params.get('parameters') # 未解析的參數字典 responses = params.get('responses') if not parameters: # 確保參數字典存在 parameters = {} for each in parameters: if each.get('in') == 'body': # body 和 query 不會同時出現 schema = each.get('schema') if schema: ref = schema.get('$ref') if ref: param_key = ref.split('/')[-1] param = self.definitions[param_key]['properties'] for key, value in param.items(): if 'example' in value.keys(): http_interface['request']['json'].update({key: value['example']}) else: http_interface['request']['json'].update({key: ''}) elif each.get('in') == 'query': name = each.get('name') for key in each.keys(): if 'example' in key: http_interface['request']['params'].update({name: each[key]}) for each in parameters: # if each.get('in') == 'path': # name = each.get('name') # for key in each.keys(): # if 'example' in key: # http_interface['request']['json'].update({name: each[key]}) # else: # # http_interface['request']['json'].update({name: ''}) if each.get('in') == 'header': name = each.get('name') for key in each.keys(): if 'example' in key: http_interface['request']['headers'].update({name: each[key]}) else: if name == 'token': http_interface['request']['headers'].update({name: '$token'}) else: http_interface['request']['headers'].update({name: ''}) for key, value in responses.items(): schema = value.get('schema') if schema: ref = schema.get('$ref') if ref: param_key = ref.split('/')[-1] res = self.definitions[param_key]['properties'] i = 0 for k, v in res.items(): if 'example' in v.keys(): http_interface['validate'].append({"eq": []}) http_interface['validate'][i]['eq'].append('content.' + k) http_interface['validate'][i]['eq'].append(v['example']) http_testcase['validate'].append({"eq": []}) http_testcase['validate'][i]['eq'].append('content.' + k) http_testcase['validate'][i]['eq'].append(v['example']) i += 1 else: http_interface['validate'].append({"eq": []}) else: http_interface['validate'].append({"eq": []}) if http_interface['request']['json'] == {}: del http_interface['request']['json'] if http_interface['request']['params'] == {}: del http_interface['request']['params'] api_path = os.path.join(os.path.abspath(os.path.join(os.path.dirname("__file__"), os.path.pardir)), 'api') tags_path = os.path.join(api_path, tag) if not os.path.exists(tags_path): os.mkdir(tags_path) json_path = os.path.join(tags_path, http_interface['name'] + '.json') write_data(http_interface, json_path) return http_testcase if __name__ == '__main__': AnalysisJson('1').retrieve_data()
簡單的實現了功能,代碼有些粗糙~~~
processingJson.py
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2019/9/9 15:18 # @Author : lixiaofeng # @Site : # @File : processingJson.py # @Software: PyCharm import json from httprunner import logger def get_json(path, field=''): """ 獲取json文件中的值,data.json和res.json可共用 :param path: :param field: :return: """ with open(path, 'r', encoding='utf-8') as f: json_data = json.load(f) if field: data = json_data.get(field) return data else: return json_data def write_data(res, json_path): """ 把處理后的參數寫入json文件 :param res: :param json_path: :return: """ if isinstance(res, dict) or isinstance(res, list): with open(json_path, 'w', encoding='utf-8') as f: json.dump(res, f, ensure_ascii=False, sort_keys=True, indent=4) logger.log_info('Interface Params Total:{} ,write to json file successfully!\n'.format(len(res))) else: logger.log_error('{} Params is not dict.\n'.format(write_data.__name__))
具體業務場景的測試,可以按照導入.har文件的方法快速生成,總之還是挺便捷的。
之前就有關注HttpRunner,終於有空花了一天時間,大致過了一遍中文文檔,簡單的記錄下學習成果~~~
關鍵字:parameters 參數化
層級很重要 testcase
{ "config": { "name": "添加發布會測試用例", "variables": {} }, "testcases": [ { "name": "添加發布會", "testcase": "testsuites/test.json", "parameters": { "limit": [ "1", "2" ] } } ] }
teststeps
{ "config": { "base_url": "http://www.easytest.xyz/api", "name": "添加發布會", "variables": { "name": "${get_random_name()}", "address": "${get_random_address()}", "eid": "${get_random_num()}", "limit": "${get_random_num_list()}" }, "output": [ "limit" ] }, "teststeps": [ { "name": "添加發布會", "request": { "headers": {}, "data": { "eid": "$eid", "name": "$name", "limit": "$limit", "address": "$address", "start_time": "2019-11-30", "status": "1" }, "method": "POST", "url": "/add_event/" }, "validate": [ { "eq": [ "content.message", "add event success" ] } ] } ] }
運行
from httprunner.api import HttpRunner runner = HttpRunner(failfast=True) runner.run(r'D:\HttpRunner_framework\testsuites\testcase_test.json') print(runner.summary)
結果