def test_parse_variables_mapping_dead_circle(self): variables = {"varA": "$varB", "varB": "123$varC"} check_variables_set = {"varA", "varB", "varC"} prepared_variables = parser.prepare_lazy_data(variables, {}, check_variables_set) with self.assertRaises(exceptions.VariableNotFound): parser.parse_variables_mapping(prepared_variables)
def test_parse_variables_mapping_ref_self(self): variables = { "varC": "${sum_two($a, $b)}", "a": 1, "b": 2, "token": "$token" } functions = {"sum_two": sum_two} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) with self.assertRaises(exceptions.VariableNotFound): parser.parse_variables_mapping(prepared_variables)
def parse_config(config: TConfig): config.variables = parse_variables_mapping( config.variables, self.__project_meta.functions) config.name = parse_data(config.name, config.variables, self.__project_meta.functions) config.base_url = parse_data(config.base_url, config.variables, self.__project_meta.functions)
def run(self, testcase: TestCase): """run testcase""" self.config = testcase.config self.teststeps = testcase.teststeps # prepare self.__project_meta = self.__project_meta or load_project_meta( self.config.path) self.__parse_config(self.config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session = self.__session or HttpSession() self.__session_variables = {} # run teststeps for step in self.teststeps: # update with config variables step.variables.update(self.config.variables) # update with session variables extracted from pre step step.variables.update(self.__session_variables) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions) # run step with allure.step(f"step: {step.name}"): extract_mapping = self.__run_step(step) # save extracted variables to session variables self.__session_variables.update(extract_mapping) self.__duration = time.time() - self.__start_at return self
def init_test_variables(self, variables_mapping=None): """ init test variables, called when each test(api) starts. init测试变量,在每个测试(api)启动时调用 variables_mapping will be evaluated first. 首先计算变量映射 Args: variables_mapping (dict) { "random": "${gen_random_string(5)}", "authorization": "${gen_md5($TOKEN, $data, $random)}", "data": '{"name": "user", "password": "******"}', "TOKEN": "debugtalk", } """ variables_mapping = variables_mapping or {} variables_mapping = utils.ensure_mapping_format(variables_mapping) variables_mapping.update(self.session_variables_mapping) parsed_variables_mapping = parser.parse_variables_mapping(variables_mapping) self.test_variables_mapping = {} # priority: extracted variable > teststep variable # 优先级:提取的变量 > teststep变量 self.test_variables_mapping.update(parsed_variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
def _add_test(test_runner, test_dict): """ add test to testcase. """ def test(self): try: test_runner.run_test(test_dict) except exceptions.MyBaseFailure as ex: self.fail(str(ex)) finally: self.meta_datas = test_runner.meta_datas if "config" in test_dict: # run nested testcase test.__doc__ = test_dict["config"].get("name") variables = test_dict["config"].get("variables", {}) else: # run api test test.__doc__ = test_dict.get("name") variables = test_dict.get("variables", {}) if isinstance(test.__doc__, parser.LazyString): try: parsed_variables = parser.parse_variables_mapping( variables) test.__doc__ = parser.parse_lazy_data( test.__doc__, parsed_variables) except exceptions.VariableNotFound: test.__doc__ = str(test.__doc__) return test
def init_test_variables(self, variables_mapping=None): """ init test variables, called when each test(api) starts. variables_mapping will be evaluated first. Args: variables_mapping (dict) { "random": "${gen_random_string(5)}", "authorization": "${gen_md5($TOKEN, $data, $random)}", "data": '{"name": "user", "password": "******"}', "TOKEN": "debugtalk", } """ variables_mapping = variables_mapping or {} variables_mapping = utils.ensure_mapping_format(variables_mapping) if "m_encoder" in self.session_variables_mapping.keys( ): # 增加判断,如果全局配置变量存在m_encoder,就删除掉 self.session_variables_mapping.pop("m_encoder") variables_mapping.update(self.session_variables_mapping) parsed_variables_mapping = parser.parse_variables_mapping( variables_mapping) self.test_variables_mapping = {} # priority: extracted variable > teststep variable self.test_variables_mapping.update(parsed_variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
def _add_test(test_runner, test_dict): """ add test to testcase. 把步骤添加到用例,返回一个测试步骤 test_dict = parsed_testcases[0][teststeps][0] test_dict = { "name": "/mgmt/store/checkBusinessAddressIsExist", "request": { "headers": {"Authorization": "LazyString(${token_type} ${access_token})"}, "method": "GET", "params": { "provinceName": "LazyString(${provinceName})", "cityName": "LazyString(${cityName})", "areaName": LazyString(${areaName}), "streetName": LazyString(${streetName}), "detailAddress": LazyString(${detailAddress})}, "url": LazyString(${base_url}/mgmt/store/checkBusinessAddressIsExist), "verify": True }, "variables": { "provinceName": "广东省", "cityName": "广州市", "areaName": "海珠区", "streetName": "南州街道", "detailAddress": "广州市海珠区南洲街道新滘中路88号唯品同创汇6区东三街17号自编23号", "access_token": LazyString(${ENV(access_token)}), "token_type": LazyString(${ENV(token_type)}), "base_url": LazyString(${ENV(base_url)}) }, "validate": [LazyFunction(equals(status_code, 200))] } """ def test(self): try: test_runner.run_test(test_dict) pprint(test_runner) except exceptions.MyBaseFailure as ex: self.fail(str(ex)) finally: self.meta_datas = test_runner.meta_datas if "config" in test_dict: # run nested testcase 嵌套testcase运行:testcase引用了testcase test.__doc__ = test_dict["config"].get("name") variables = test_dict["config"].get("variables", {}) else: # run api test 运行api测试 testcase引用了api test.__doc__ = test_dict.get("name") variables = test_dict.get("variables", {}) if isinstance(test.__doc__, parser.LazyString): #懒惰的字符串:名字中有引用的变量 try: parsed_variables = parser.parse_variables_mapping( variables) # 所有的变量字典 test.__doc__ = parser.parse_lazy_data( # 找到引用变量对应的值 test.__doc__, parsed_variables) except exceptions.VariableNotFound: test.__doc__ = str(test.__doc__) # 返回函数<function HttpRunner._add_tests.<locals>._add_test.<locals>.test at 0x000002D69C38A550> return test
def __parse_config(self, config: TConfig) -> NoReturn: config.variables.update(self.__session_variables) config.variables = parse_variables_mapping( config.variables, self.__project_meta.functions) config.name = parse_data(config.name, config.variables, self.__project_meta.functions) config.base_url = parse_data(config.base_url, config.variables, self.__project_meta.functions)
def test_parse_variables_mapping_fix_duplicate_function_call(self): # fix duplicate function calling variables = {"varA": "$varB", "varB": "${gen_random_string(5)}"} functions = {"gen_random_string": gen_random_string} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) parsed_variables = parser.parse_variables_mapping(prepared_variables) self.assertEqual(parsed_variables["varA"], parsed_variables["varB"])
def test_parse_variables_mapping(self): variables = { "varA": "$varB", "varB": "$varC", "varC": "123", "a": 1, "b": 2 } parsed_variables = parser.parse_variables_mapping(variables) print(parsed_variables) self.assertEqual(parsed_variables["varA"], "123") self.assertEqual(parsed_variables["varB"], "123")
def test_parse_tests_variable_with_function(self): tests_mapping = { "project_mapping": { "functions": { "sum_two": sum_two, "gen_random_string": gen_random_string } }, 'testcases': [ { "config": { 'name': '', "base_url": "$host1", 'variables': { "host1": "https://debugtalk.com", "var_a": "${gen_random_string(5)}", "var_b": "$var_a" } }, "teststeps": [ { 'name': 'testcase1', "base_url": "$host2", "variables": { "host2": "https://httprunner.org", "num3": "${sum_two($num2, 4)}", "num2": "${sum_two($num1, 3)}", "num1": "${sum_two(1, 2)}", "str1": "${gen_random_string(5)}", "str2": "$str1" }, 'request': { 'url': '/api1/?num1=$num1&num2=$num2&num3=$num3', 'method': 'GET' } } ] } ] } parsed_testcases = parser.parse_tests(tests_mapping) test_dict = parsed_testcases[0]["teststeps"][0] variables = parser.parse_variables_mapping(test_dict["variables"]) self.assertEqual(variables["num3"], 10) self.assertEqual(variables["num2"], 6) parsed_test_dict = parser.parse_lazy_data(test_dict, variables) self.assertEqual(parsed_test_dict["base_url"], "https://httprunner.org") self.assertEqual( parsed_test_dict["request"]["url"], "/api1/?num1=3&num2=6&num3=10" ) self.assertEqual(variables["str1"], variables["str2"])
def prepare_upload_step(step: TStep, functions: FunctionsMapping) -> "NoReturn": """ preprocess for upload test replace `upload` info with MultipartEncoder Args: step: teststep { "variables": {}, "request": { "url": "http://httpbin.org/upload", "method": "POST", "headers": { "Cookie": "session=AAA-BBB-CCC" }, "upload": { "file": "data/file_to_upload" "md5": "123" } } } functions: functions mapping """ if not step.request.upload: return if not UPLOAD_READY: msg = """ uploader extension dependencies uninstalled, install first and try again. install with pip: $ pip install requests_toolbelt filetype """ logger.error(msg) sys.exit(1) params_list = [] for key, value in step.request.upload.items(): step.variables[key] = value params_list.append(f"{key}=${key}") params_str = ", ".join(params_list) step.variables["m_encoder"] = "${multipart_encoder(" + params_str + ")}" # parse variables step.variables = parse_variables_mapping(step.variables, functions) step.request.headers[ "Content-Type"] = "${multipart_content_type($m_encoder)}" step.request.data = "$m_encoder"
def test_parse_variables_mapping_2(self): variables = { "host2": "https://httprunner.org", "num3": "${sum_two($num2, 4)}", "num2": "${sum_two($num1, 3)}", "num1": "${sum_two(1, 2)}" } functions = {"sum_two": sum_two} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) parsed_testcase = parser.parse_variables_mapping(prepared_variables) self.assertEqual(parsed_testcase["num3"], 10) self.assertEqual(parsed_testcase["num2"], 6) self.assertEqual(parsed_testcase["num1"], 3)
def test_parse_variables_mapping(self): variables = { "varA": "123$varB", "varB": "456$varC", "varC": "${sum_two($a, $b)}", "a": 1, "b": 2 } functions = {"sum_two": sum_two} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) parsed_variables = parser.parse_variables_mapping(prepared_variables) self.assertEqual(parsed_variables["varA"], "1234563") self.assertEqual(parsed_variables["varB"], "4563") self.assertEqual(parsed_variables["varC"], 3)
def run_testcase(self, testcase: TestCase) -> "HttpRunner": """run specified testcase Examples: >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)]) >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) """ self.__config = testcase.config self.__teststeps = testcase.teststeps # prepare self.__project_meta = self.__project_meta or load_project_meta( self.__config.path ) self.__parse_config(self.__config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session = self.__session or HttpSession() self.__session_variables = {} # run teststeps for step in self.__teststeps: # override variables # session variables (extracted from pre step) > step variables step.variables.update(self.__session_variables) # step variables > testcase config variables step.variables = override_config_variables( step.variables, self.__config.variables ) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions ) # run step if USE_ALLURE: with allure.step(f"step: {step.name}"): extract_mapping = self.__run_step(step) else: extract_mapping = self.__run_step(step) # save extracted variables to session variables self.__session_variables.update(extract_mapping) self.__duration = time.time() - self.__start_at return self
def test_parse_variables_mapping_dollar_notation(self): variables = { "varA": "123$varB", "varB": "456$$0", "varC": "${sum_two($a, $b)}", "a": 1, "b": 2, "c": "abc" } functions = {"sum_two": sum_two} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) parsed_testcase = parser.parse_variables_mapping(prepared_variables) self.assertEqual(parsed_testcase["varA"], "123456$0") self.assertEqual(parsed_testcase["varB"], "456$0") self.assertEqual(parsed_testcase["varC"], 3)
def test_prepare_lazy_data_dual_dollar(self): variables = { "num0": 123, "var1": "abc$$num0", "var2": "abc$$$num0", "var3": "abc$$$$num0", } functions = {"sum_two": sum_two} prepared_variables = parser.prepare_lazy_data(variables, functions, variables.keys()) self.assertEqual(prepared_variables["var1"], "abc$num0") self.assertIsInstance(prepared_variables["var2"], parser.LazyString) self.assertEqual(prepared_variables["var3"], "abc$$num0") parsed_variables = parser.parse_variables_mapping(prepared_variables) self.assertEqual(parsed_variables["var1"], "abc$num0") self.assertEqual(parsed_variables["var2"], "abc$123") self.assertEqual(parsed_variables["var3"], "abc$$num0")
def prepare_upload_step(step: TStep, functions: FunctionsMapping): """preprocess for upload test replace `upload` info with MultipartEncoder Args: step: teststep { "variables": {}, "request": { "url": "http://httpbin.org/upload", "method": "POST", "headers": { "Cookie": "session=AAA-BBB-CCC" }, "upload": { "file": "data/file_to_upload" "md5": "123" } } } functions: functions mapping """ if not step.request.upload: return # parse upload info step.request.upload = parse_data(step.request.upload, step.variables, functions) ensure_upload_ready() params_list = [] for key, value in step.request.upload.items(): step.variables[key] = value params_list.append(f"{key}=${key}") params_str = ", ".join(params_list) step.variables["m_encoder"] = "${multipart_encoder(" + params_str + ")}" # parse variables step.variables = parse_variables_mapping(step.variables, functions) step.request.headers["Content-Type"] = "${multipart_content_type($m_encoder)}" step.request.data = "$m_encoder"
def test_get_parsed_request(self): variables = { "random": "${gen_random_string(5)}", "data": '{"name": "user", "password": "******"}', "authorization": "${gen_md5($TOKEN, $data, $random)}", "TOKEN": "debugtalk" } functions = { "gen_random_string": gen_random_string, "gen_md5": gen_md5 } variables = parser.prepare_lazy_data(variables, functions, variables.keys()) variables = parser.parse_variables_mapping(variables) self.context.init_test_variables(variables) request = { "url": "http://127.0.0.1:5000/api/users/1000", "method": "POST", "headers": { "Content-Type": "application/json", "authorization": "$authorization", "random": "$random", "secret_key": "$SECRET_KEY" }, "data": "$data" } prepared_request = parser.prepare_lazy_data( request, functions, {"authorization", "random", "SECRET_KEY", "data"}) parsed_request = self.context.eval_content(prepared_request) self.assertIn("authorization", parsed_request["headers"]) self.assertEqual(len(parsed_request["headers"]["authorization"]), 32) self.assertIn("random", parsed_request["headers"]) self.assertEqual(len(parsed_request["headers"]["random"]), 5) self.assertIn("data", parsed_request) self.assertEqual(parsed_request["data"], '{"name": "user", "password": "******"}') self.assertEqual(parsed_request["headers"]["secret_key"], "DebugTalk")
def run(self, testcase: TestCase): """main entrance""" self.config = testcase.config self.teststeps = testcase.teststeps self.config.variables.update(self.__session_variables) if self.config.path: self.__project_meta = load_project_meta(self.config.path) elif not self.__project_meta: self.__project_meta = ProjectMeta() def parse_config(config: TConfig): config.variables = parse_variables_mapping( config.variables, self.__project_meta.functions) config.name = parse_data(config.name, config.variables, self.__project_meta.functions) config.base_url = parse_data(config.base_url, config.variables, self.__project_meta.functions) parse_config(self.config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session_variables = {} for step in self.teststeps: # update with config variables step.variables.update(self.config.variables) # update with session variables extracted from pre step step.variables.update(self.__session_variables) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions) # run step extract_mapping = self.__run_step(step) # save extracted variables to session variables self.__session_variables.update(extract_mapping) self.__duration = time.time() - self.__start_at return self
def test_init_test_variables(self): variables = { "random": "${gen_random_string($num)}", "authorization": "${gen_md5($TOKEN, $data, $random)}", "data": "$username", # TODO: escape '{' and '}' # "data": '{"name": "$username", "password": "******"}', "TOKEN": "debugtalk", "username": "******", "num": 6 } functions = { "gen_random_string": gen_random_string, "gen_md5": gen_md5 } variables = parser.prepare_lazy_data(variables, functions, variables.keys()) variables = parser.parse_variables_mapping(variables) self.context.init_test_variables(variables) variables_mapping = self.context.test_variables_mapping self.assertEqual(len(variables_mapping["random"]), 6) self.assertEqual(len(variables_mapping["authorization"]), 32) self.assertEqual(variables_mapping["data"], 'user1')
def _aggregate(self, testcases_results, testcase_details=None): """ aggregate results Args: testcases_results (list): list of (test_case, result) """ summary = { "success": True, "stat": { "testcases": { "total": len(testcases_results), "success": 0, "fail": 0 }, "teststeps": {} }, "time": {}, "platform": report.get_platform(), "details": [] } # custom for index_case, tests_result in enumerate(testcases_results): testcase, result = tests_result testcase_summary = report.get_summary(result) if testcase_summary["success"]: summary["stat"]["testcases"]["success"] += 1 else: summary["stat"]["testcases"]["fail"] += 1 summary["success"] &= testcase_summary["success"] testcase_summary["name"] = testcase.config.get("name") testcase_summary["in_out"] = utils.get_testcase_io(testcase) report.aggregate_stat( summary["stat"]["teststeps"], testcase_summary["stat"]) report.aggregate_stat(summary["time"], testcase_summary["time"]) # ================================================== # custom: add step detail to summary, by zheng.zhang # step detail path in summary: summary.details[i].records[j].step_detail teststeps = testcase.teststeps for index_step, teststep in enumerate(teststeps): try: step_detail = parser.parse_variables_mapping(teststep["variables"]) testcase_summary["records"][index_step]["step_detail"] = step_detail except exceptions.VariableNotFound as e: # TODO: deal with various built-in Exception testcase_summary["records"][index_step]["step_detail"] = { "internel error message": \ "error during adding step details: VariableNotFound", "Exception message": e } # print("error during adding step detail to summary, \ # in {}".format(__file__)) # ================================================== summary["details"].append(testcase_summary) return summary
def test_parse_variables_mapping_exception(self): variables = {"varA": "$varB", "varB": "$varC", "a": 1, "b": 2} with self.assertRaises(VariableNotFound): parser.parse_variables_mapping(variables)
def __init__(self, variables=None): variables_mapping = utils.ensure_mapping_format(variables or {}) self.session_variables_mapping = parser.parse_variables_mapping( variables_mapping) self.test_variables_mapping = {} self.init_test_variables()
def run_testcase(self, testcase: TestCase) -> "HttpRunner": """run specified testcase Examples: >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)]) >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) """ self.__config = testcase.config self.__teststeps = testcase.teststeps # prepare self.__project_meta = self.__project_meta or load_project_meta( self.__config.path) self.__parse_config(self.__config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session = self.__session or HttpSession() # save extracted variables of teststeps extracted_variables: VariablesMapping = {} # run teststeps for step in self.__teststeps: # override variables # step variables > extracted variables from previous steps step.variables = merge_variables(step.variables, extracted_variables) # step variables > testcase config variables step.variables = merge_variables(step.variables, self.__config.variables) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions) while True: # run step if USE_ALLURE: with allure.step(f"step: {step.name}"): extract_mapping = self.__run_step(step) else: extract_mapping = self.__run_step(step) if step.retry_whens: variables_mapping = step.variables variables_mapping.update(extract_mapping) try: response = step.variables.get( 'response') or ResponseObject(requests.Response()) if isinstance(response, ResponseObject): response.validate(step.retry_whens, variables_mapping, self.__project_meta.functions) else: break except ValidationFailure: break else: break # save extracted variables to session variables extracted_variables.update(extract_mapping) self.__session_variables.update(extracted_variables) self.__duration = time.time() - self.__start_at return self
def run_testcase(self, testcase: TestCase) -> "HttpRunner": """run specified testcase Examples: >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)]) >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) """ self.__config = testcase.config self.__teststeps = testcase.teststeps # prepare self.__project_meta = self.__project_meta or load_project_meta( self.__config.path) self.__parse_config(self.__config) self.__start_at = time.time() self.__step_datas: List[StepData] = [] self.__session = self.__session or HttpSession() # save extracted variables of teststeps extracted_variables: VariablesMapping = {} # run teststeps for step in self.__teststeps: # override variables # step variables > extracted variables from previous steps step.variables = merge_variables(step.variables, extracted_variables) # step variables > testcase config variables step.variables = merge_variables(step.variables, self.__config.variables) # parse variables step.variables = parse_variables_mapping( step.variables, self.__project_meta.functions) step.skipif = parse_data(step.skipif, step.variables, self.__project_meta.functions) # 跳过满足条件的步骤 logger.debug(f"[跳过步骤] skipif={step.skipif}") if step.skipif == '': continue if step.skipif and eval(step.skipif): logger.debug( f"[满足条件,跳过步骤] skipif={step.skipif} | step_name={step.name}" ) continue # run step if USE_ALLURE: with allure.step(f"step: {step.name}"): extract_mapping = self.__run_step(step) else: extract_mapping = self.__run_step(step) # 每运行一个步骤就重新加载公共变量 logger.debug(f"step.variables={step.variables}") step.variables.clear() # save extracted variables to session variables extracted_variables.update(extract_mapping) self.__session_variables.update(extracted_variables) self.__duration = time.time() - self.__start_at return self