接口自動化是指模擬程序接口層面的自動化,因爲接口不易變動,維護成本更小,因此深受各大公司的喜好。
初版入口:接口自動化框架(Pytest+request+Allure)
本次版本作了一些升級,增長了自動生成testcase等,一塊兒來看看吧!~~html
環境:Mac+Python 3+Pytest+Allure+Request
流程:Charles導出接口數據-自動生成測試用例-修改測試用例-執行測試用例-生成Allure報告
開源: 點擊這裏,跳轉到github
備註⚠️:Charles導出接口應選擇文件類型爲JSON Session File(.chlsj)
git
重要模塊介紹:
一、writeCase.py :自動讀取新的Charles文件,並自動生成測試用例
二、apiMethod.py:封裝request方法,能夠支持多協議擴展(get\post\put)
三、checkResult.py:封裝驗證response方法
四、setupMain.py: 核心代碼,定義並執行用例集,生成報告
___github
一、測試數據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:
二、測試case(自動生成的case.py)shell
@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)
三、writeCase.py (封裝方法:自動生成測試case)json
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])
四、apiMethod.py(封裝方法:http多協議)api
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
五、checkResult.py(封裝方法:校驗response結果)cookie
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"])
六、setupMain.py(執行用例集,生成測試報告)app
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)
七、測試報告
框架
以上,喜歡的話請點贊❤️吧~
歡迎關注個人簡書,博客,TesterHome,Github~~~