前言: 接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱。 第一版入口:接口自动化框架(Pytest+request+Allure) 本次版本做了一些
前言:
接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱。
第一版入口:接口自动化框架(Pytest+request+Allure)
本次版本做了一些升级,增加了自动生成testcase等,一起来看看吧!~~
一、简单介绍
环境:Mac+Python 3+Pytest+Allure+Request
流程:Charles导出接口数据-自动生成测试用例-修改测试用例-执行测试用例-生成Allure报告
开源: 点击这里,跳转到github
备注??:Charles导出接口应选择文件类型为JSON Session File(.chlsj)
重要模块介绍:
1、writeCase.py :自动读取新的Charles文件,并自动生成测试用例
2、apiMethod.py:封装request方法,可以支持多协议扩展(get\post\put)
3、checkResult.py:封装验证response方法
4、setupMain.py: 核心代码,定义并执行用例集,生成报告
___
二、目录介绍
三、代码分析
1、测试数据yml(自动生成的yml文件)
# 用例基本信息 test_info: # 用例标题,在报告中作为一级目录显示 title: blogpost # 用例ID id: test_reco_01 # 请求的域名,可写死,也可写成模板关联host配置文件 host: ${host}$ # 请求地址 选填(此处不填,每条用例必填) address: /api/v2/recomm/blogpost/reco # 前置条件,case之前需关联的接口 premise: # 测试用例 test_case: - test_name: reco_1 # 第一条case,info可不填 info: reco # 请求协议 http_type: https # 请求类型 request_type: POST # 参数类型 parameter_type: application/json # 请求地址 address: /api/v2/recomm/blogpost/reco # 请求头 headers: # parameter为文件路径时 parameter: reco.json # 是否需要获取cookie cookies: False # 是否为上传文件的接口 file: false # 超时时间 timeout: 20 # 校验列表 list or dict # 不校验时 expected_code, expected_request 均可不填 check: expected_request: result_reco.json check_type: only_check_status expected_code: 503 # 关联键 relevance:
2、测试case(自动生成的case.py)
@allure.feature(case_dict["test_info"]["title"]) class TestReco: @pytest.mark.parametrize("case_data", case_dict["test_case"], ids=[]) @allure.story("reco") @pytest.mark.flaky(reruns=3, reruns_delay=3) def test_reco(self, case_data): """ :param case_data: 测试用例 :return: """ self.init_relevance = ini_request(case_dict, PATH) # 发送测试请求 api_send_check(case_data, case_dict, self.init_relevance, PATH)
3、writeCase.py (封装方法:自动生成测试case)
def write_case(_path): yml_list = write_case_yml(_path) project_path = str(os.path.abspath('.').split('/bin')[0]) test_path = project_path+'/aff/testcase/' src = test_path+'Template.py' for case in yml_list: yml_path = case.split('/')[0] yml_name = case.split('/')[1] case_name = 'test_' + yml_name + '.py' new_case = test_path + yml_path + '/' + case_name mk_dir(test_path + yml_path) if case_name in os.listdir(test_path + yml_path): pass else: shutil.copyfile(src, new_case) with open(new_case, 'r') as fw: source = fw.readlines() n = 0 with open(new_case, 'w') as f: for line in source: if 'PATH = setupMain.PATH' in line: line = line.replace("/aff/page/offer", "/aff/page/%s" % yml_path) f.write(line) n = n+1 elif 'case_dict = ini_case' in line: line = line.replace("Template", yml_name) f.write(line) n = n + 1 elif 'class TestTemplate' in line: line = line.replace("TestTemplate", "Test%s" % yml_name.title().replace("_", "")) f.write(line) n = n + 1 elif '@allure.story' in line: line = line.replace("Template", yml_name) f.write(line) n = n + 1 elif 'def test_template' in line: line = line.replace("template", yml_name.lower()) f.write(line) n = n + 1 else: f.write(line) n += 1 for i in range(n, len(source)): f.write(source[i])
4、apiMethod.py(封装方法:http多协议)
def post(header, address, request_parameter_type, timeout=8, data=None, files=None): """ post请求 :param header: 请求头 :param address: 请求地址 :param request_parameter_type: 请求参数格式(form_data,raw) :param timeout: 超时时间 :param data: 请求参数 :param files: 文件路径 :return: """ if 'form_data' in request_parameter_type: for i in files: value = files[i] if '/' in value: file_parm = i files[file_parm] = (os.path.basename(value), open(value, 'rb')) enc = MultipartEncoder( fields=files, boundary='--------------' + str(random.randint(1e28, 1e29 - 1)) ) header['Content-Type'] = enc.content_type response = requests.post(url=address, data=enc, headers=header, timeout=timeout) else: response = requests.post(url=address, data=data, headers=header, timeout=timeout, files=files) try: if response.status_code != 200: return response.status_code, response.text else: return response.status_code, response.json() except json.decoder.JSONDecodeError: return response.status_code, '' except simplejson.errors.JSONDecodeError: return response.status_code, '' except Exception as e: logging.exception('ERROR') logging.error(e) raise
5、checkResult.py(封装方法:校验response结果)
def check_result(test_name, case, code, data, _path, relevance=None): """ 校验测试结果 :param test_name: 测试名称 :param case: 测试用例 :param code: HTTP状态 :param data: 返回的接口json数据 :param relevance: 关联值对象 :param _path: case路径 :return: """ # 不校验结果 if case["check_type"] == 'no_check': with allure.step("不校验结果"): pass # json格式校验 elif case["check_type"] == 'json': expected_request = case["expected_request"] if isinstance(case["expected_request"], str): expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance) with allure.step("JSON格式校验"): allure.attach("期望code", str(case["expected_code"])) allure.attach('期望data', str(expected_request)) allure.attach("实际code", str(code)) allure.attach('实际data', str(data)) if int(code) == case["expected_code"]: if not data: data = "{}" check_json(expected_request, data) else: raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) # 只校验状态码 elif case["check_type"] == 'only_check_status': with allure.step("校验HTTP状态"): allure.attach("期望code", str(case["expected_code"])) allure.attach("实际code", str(code)) allure.attach('实际data', str(data)) if int(code) == case["expected_code"]: pass else: raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) # 完全校验 elif case["check_type"] == 'entirely_check': expected_request = case["expected_request"] if isinstance(case["expected_request"], str): expected_request = readExpectedResult.read_json(test_name, expected_request, _path, relevance) with allure.step("完全校验"): allure.attach("期望code", str(case["expected_code"])) allure.attach('期望data', str(expected_request)) allure.attach("实际code", str(code)) allure.attach('实际data', str(data)) if int(code) == case["expected_code"]: result = operator.eq(expected_request, data) if result: pass else: raise Exception("完全校验失败! %s ! = %s" % (expected_request, data)) else: raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) # 正则校验 elif case["check_type"] == 'Regular_check': if int(code) == case["expected_code"]: try: result = "" if isinstance(case["expected_request"], list): for i in case[""]: result = re.findall(i.replace("\"","\""), str(data)) allure.attach('校验完成结果\n',str(result)) else: result = re.findall(case["expected_request"].replace("\"", "\'"), str(data)) with allure.step("正则校验"): allure.attach("期望code", str(case["expected_code"])) allure.attach('正则表达式', str(case["expected_request"]).replace("\'", "\"")) allure.attach("实际code", str(code)) allure.attach('实际data', str(data)) allure.attach(case["expected_request"].replace("\"", "\'") + '校验完成结果', str(result).replace("\'", "\"")) if not result: raise Exception("正则未校验到内容! %s" % case["expected_request"]) except KeyError: raise Exception("正则校验执行失败! %s\n正则表达式为空时" % case["expected_request"]) else: raise Exception("http状态码错误!\n %s != %s" % (code, case["expected_code"])) else: raise Exception("无该校验方式%s" % case["check_type"])
6、setupMain.py(执行用例集,生成测试报告)
def invoke(md): output, errors = subprocess.Popen(md, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() o = output.decode("utf-8") return o if __name__ == '__main__': LogConfig(PATH) write_case(har_path) args = ['-s', '-q', '--alluredir', xml_report_path] pytest.main(args) cmd = 'allure generate %s -o %s' % (xml_report_path, html_report_path) invoke(cmd)
7、测试报告
以上,喜欢的话请点赞??吧~
欢迎关注我的简书,博客,TesterHome,Github~~~