def __parse_config(config, project_mapping): """ parse testcase/testsuite config, include variables and name. """ # get config variables raw_config_variables = config.pop("variables", {}) raw_config_variables_mapping = utils.ensure_mapping_format( raw_config_variables) override_variables = utils.deepcopy_dict( project_mapping.get("variables", {})) functions = project_mapping.get("functions", {}) # override config variables with passed in variables raw_config_variables_mapping.update(override_variables) # parse config variables parsed_config_variables = {} for key, value in raw_config_variables_mapping.items(): parsed_value = parse_data(value, raw_config_variables_mapping, functions, raise_if_variable_not_found=False) parsed_config_variables[key] = parsed_value if parsed_config_variables: config["variables"] = parsed_config_variables # parse config name config["name"] = parse_data(config.get("name", ""), parsed_config_variables, functions) # parse config base_url if "base_url" in config: config["base_url"] = parse_data(config["base_url"], parsed_config_variables, functions)
def __prepare_config(config, project_mapping, session_variables_set=None): """ parse testcase/testsuite config. """ # get config variables raw_config_variables = config.pop("variables", {}) raw_config_variables_mapping = utils.ensure_mapping_format( raw_config_variables) # 检查变量格式 override_variables = utils.deepcopy_dict( project_mapping.get("variables", {})) # deepcopy字典 functions = project_mapping.get("functions", {}) # override config variables with passed in variables raw_config_variables_mapping.update(override_variables) if raw_config_variables_mapping: config["variables"] = raw_config_variables_mapping check_variables_set = set(raw_config_variables_mapping.keys()) check_variables_set |= ( session_variables_set or set() ) # check_variables_set = check_variables_set | (session_variables_set or set()) True False 取True,都是False 取Fasle prepared_config = prepare_lazy_data(config, functions, check_variables_set, cached=True) return prepared_config
def _extend_with_testcase(test_dict, testcase_def_dict): """ extend test with testcase definition test will merge and override testcase config definition. Args: test_dict (dict): test block testcase_def_dict (dict): testcase definition Returns: dict: extended test dict. """ # override testcase config variables testcase_def_dict["config"].setdefault("variables", {}) testcase_def_variables = utils.ensure_mapping_format(testcase_def_dict["config"].get("variables", {})) testcase_def_variables.update(test_dict.pop("variables", {})) testcase_def_dict["config"]["variables"] = testcase_def_variables # override base_url, verify # priority: testcase config > testsuite tests test_base_url = test_dict.pop("base_url", "") if not testcase_def_dict["config"].get("base_url"): testcase_def_dict["config"]["base_url"] = test_base_url test_verify = test_dict.pop("verify", True) testcase_def_dict["config"].setdefault("verify", test_verify) # override testcase config name, output, etc. testcase_def_dict["config"].update(test_dict) test_dict.clear() test_dict.update(testcase_def_dict)
def extract_response(self, extractors): """ extract value from requests.Response and store in OrderedDict. Args: extractors (list): [ {"resp_status_code": "status_code"}, {"resp_headers_content_type": "headers.content-type"}, {"resp_content": "content"}, {"resp_content_person_first_name": "content.person.name.first_name"} ] Returns: OrderDict: variable binds ordered dict """ if not extractors: return {} logger.log_debug("start to extract from response object.") extracted_variables_mapping = OrderedDict() extract_binds_order_dict = utils.ensure_mapping_format(extractors) for key, field in extract_binds_order_dict.items(): extracted_variables_mapping[key] = self.extract_field(field) return extracted_variables_mapping
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 parse_lazy_data(content, variables_mapping=None): """ parse lazy data with evaluated variables mapping. Notice: variables_mapping should not contain any variable or function. """ # TODO: refactor type check if content is None or isinstance(content, (numeric_types, bool, type)): return content elif isinstance(content, LazyString): variables_mapping = utils.ensure_mapping_format(variables_mapping or {}) return content.to_value(variables_mapping) elif isinstance(content, (list, set, tuple)): return [parse_lazy_data(item, variables_mapping) for item in content] elif isinstance(content, dict): parsed_content = {} for key, value in content.items(): parsed_key = parse_lazy_data(key, variables_mapping) parsed_value = parse_lazy_data(value, variables_mapping) parsed_content[parsed_key] = parsed_value return parsed_content return content
def __prepare_config(config, project_mapping, session_variables_set=None): """ parse testcase/testsuite config. """ # get config variables raw_config_variables = config.pop("variables", {}) override_variables = utils.deepcopy_dict(project_mapping.get("variables", {})) functions = project_mapping.get("functions", {}) if isinstance(raw_config_variables, basestring) and function_regex_compile.match( raw_config_variables): # config variables are generated by calling function # e.g. # "config": { # "name": "basic test with httpbin", # "variables": "${gen_variables()}" # } raw_config_variables_mapping = parse_lazy_data( prepare_lazy_data(raw_config_variables, functions_mapping=functions) ) else: raw_config_variables_mapping = utils.ensure_mapping_format(raw_config_variables) # override config variables with passed in variables raw_config_variables_mapping.update(override_variables) if raw_config_variables_mapping: config["variables"] = raw_config_variables_mapping check_variables_set = set(raw_config_variables_mapping.keys()) check_variables_set |= (session_variables_set or set()) prepared_config = prepare_lazy_data(config, functions, check_variables_set, cached=True) return prepared_config
def update_session_variables(self, variables_mapping): """ update session with extracted variables mapping. these variables are valid in the whole running session. """ variables_mapping = utils.ensure_mapping_format(variables_mapping) self.session_variables_mapping.update(variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
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 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) self.test_variables_mapping = {} # priority: extracted variable > teststep variable self.test_variables_mapping.update(variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping) for variable_name, variable_value in variables_mapping.items(): variable_value = self.eval_content(variable_value) self.update_test_variables(variable_name, variable_value)
def test_ensure_mapping_format(self): map_list = [ {"a": 1}, {"b": 2} ] ordered_dict = utils.ensure_mapping_format(map_list) self.assertIsInstance(ordered_dict, dict) self.assertIn("a", ordered_dict)
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None): """ parse parameters and generate cartesian product. 解析参数并生成笛卡尔积 Args: parameters (list) parameters: parameter name and value in list参数(列表)参数:列表中的参数名称和值 parameter value may be in three types:参数值可以有三种类型 (1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"] (2) call built-in parameterize function, "${parameterize(account.csv)}" (3) call custom function in debugtalk.py, "${gen_app_version()}" variables_mapping (dict): variables mapping loaded from testcase config从testcase配置加载的变量映射 functions_mapping (dict): functions mapping loaded from debugtalk.py函数映射:从debugtalk.py加载的函数映射 Returns: list: cartesian product list笛卡儿产品列表 Examples: >>> parameters = [ {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, {"username-password": "******"}, {"app_version": "${gen_app_version()}"} ] >>> parse_parameters(parameters) """ variables_mapping = variables_mapping or {} functions_mapping = functions_mapping or {} parsed_parameters_list = [] parameters = utils.ensure_mapping_format(parameters) # 确保映射的格式是字典 for parameter_name, parameter_content in parameters.items(): parameter_name_list = parameter_name.split("-") if isinstance(parameter_content, list): # (1) data list # e.g. {"app_version": ["2.8.5", "2.8.6"]} # => [{"app_version": "2.8.5", "app_version": "2.8.6"}] # e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]} # => [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}] parameter_content_list = [] for parameter_item in parameter_content: if not isinstance(parameter_item, (list, tuple)): # "2.8.5" => ["2.8.5"] parameter_item = [parameter_item] # ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"} # ["username", "password"], ["user1", "111111"] => {"username": "******", "password": "******"} parameter_content_dict = dict(zip(parameter_name_list, parameter_item)) parameter_content_list.append(parameter_content_dict) elif: parsed_variables_mapping = parse_variables_mapping( variables_mapping ) return
def update_session_variables(self, variables_mapping): """ update session with extracted variables mapping. 用提取的变量映射更新会话 these variables are valid in the whole running session. 这些变量在整个运行的会话中是有效的 """ variables_mapping = utils.ensure_mapping_format(variables_mapping) self.session_variables_mapping.update(variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
def __prepare_testcase_tests(tests, config, project_mapping, session_variables_set=None): """ override tests with testcase config variables, base_url and verify. test maybe nested testcase. variables priority: testcase config > testcase test > testcase_def config > testcase_def test > api base_url priority: testcase test > testcase config > testsuite test > testsuite config > api verify priority: testcase teststep (api) > testcase config > testsuite config Args: tests (list): config (dict): project_mapping (dict): """ config_variables = config.get("variables", {}) config_base_url = config.get("base_url", "") config_verify = config.get("verify", True) functions = project_mapping.get("functions", {}) prepared_testcase_tests = [] session_variables_set = set( config_variables.keys()) | (session_variables_set or set()) for test_dict in tests: teststep_variables_set = {"request", "response"} # 1, testcase config => testcase tests # override test_dict variables test_dict_variables = utils.extend_variables( test_dict.pop("variables", {}), config_variables) test_dict["variables"] = test_dict_variables # base_url & verify: priority test_dict > config if (not test_dict.get("base_url")) and config_base_url: test_dict["base_url"] = config_base_url # unify validators' format if "validate" in test_dict: ref_raw_validators = test_dict.pop("validate", []) test_dict["validate"] = [ validator.uniform_validator(_validator) for _validator in ref_raw_validators ] if "testcase_def" in test_dict: # test_dict is nested testcase # pass former teststep's (as a testcase) export value to next teststep # Since V2.2.2, `extract` is used to replace `output`, # `output` is also kept for compatibility if "extract" in test_dict: session_variables_set |= set(test_dict["extract"]) elif "output" in test_dict: # kept for compatibility session_variables_set |= set(test_dict["output"]) # 2, testcase test_dict => testcase_def config testcase_def = test_dict.pop("testcase_def") _extend_with_testcase(test_dict, testcase_def) # verify priority: nested testcase config > testcase config test_dict["config"].setdefault("verify", config_verify) # 3, testcase_def config => testcase_def test_dict test_dict = _parse_testcase(test_dict, project_mapping, session_variables_set) elif "api_def" in test_dict: # test_dict has API reference # 2, test_dict => api api_def_dict = test_dict.pop("api_def") _extend_with_api(test_dict, api_def_dict) # current teststep variables teststep_variables_set |= set(test_dict.get("variables", {}).keys()) # verify priority: testcase teststep > testcase config if "request" in test_dict and "verify" not in test_dict["request"]: test_dict["request"]["verify"] = config_verify # move extracted variable to session variables if "extract" in test_dict: extract_mapping = utils.ensure_mapping_format(test_dict["extract"]) session_variables_set |= set(extract_mapping.keys()) teststep_variables_set |= session_variables_set # convert validators to lazy function validators = test_dict.pop("validate", []) prepared_validators = [] for _validator in validators: function_meta = { "func_name": _validator["comparator"], "args": [_validator["check"], _validator["expect"]], "kwargs": {} } prepared_validators.append( LazyFunction(function_meta, functions, teststep_variables_set)) test_dict["validate"] = prepared_validators # convert variables and functions to lazy object. # raises VariableNotFound if undefined variable exists in test_dict prepared_test_dict = prepare_lazy_data(test_dict, functions, teststep_variables_set) prepared_testcase_tests.append(prepared_test_dict) return prepared_testcase_tests
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None): """ parse parameters and generate cartesian product. Args: parameters (list) parameters: parameter name and value in list parameter value may be in three types: (1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"] (2) call built-in parameterize function, "${parameterize(account.csv)}" (3) call custom function in debugtalk.py, "${gen_app_version()}" variables_mapping (dict): variables mapping loaded from testcase config functions_mapping (dict): functions mapping loaded from debugtalk.py Returns: list: cartesian product list Examples: >>> parameters = [ {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, {"username-password": "******"}, {"app_version": "${gen_app_version()}"} ] >>> parse_parameters(parameters) """ variables_mapping = variables_mapping or {} functions_mapping = functions_mapping or {} parsed_parameters_list = [] parameters = utils.ensure_mapping_format(parameters) for parameter_name, parameter_content in parameters.items(): parameter_name_list = parameter_name.split("-") if isinstance(parameter_content, list): # (1) data list # e.g. {"app_version": ["2.8.5", "2.8.6"]} # => [{"app_version": "2.8.5", "app_version": "2.8.6"}] # e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]} # => [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}] parameter_content_list = [] for parameter_item in parameter_content: if not isinstance(parameter_item, (list, tuple)): # "2.8.5" => ["2.8.5"] parameter_item = [parameter_item] # ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"} # ["username", "password"], ["user1", "111111"] => {"username": "******", "password": "******"} parameter_content_dict = dict( zip(parameter_name_list, parameter_item)) parameter_content_list.append(parameter_content_dict) else: # (2) & (3) parsed_variables_mapping = parse_variables_mapping( variables_mapping) parsed_parameter_content = eval_lazy_data( parameter_content, parsed_variables_mapping, functions_mapping) if not isinstance(parsed_parameter_content, list): raise exceptions.ParamsError("parameters syntax error!") parameter_content_list = [] for parameter_item in parsed_parameter_content: if isinstance(parameter_item, dict): # get subset by parameter name # {"app_version": "${gen_app_version()}"} # gen_app_version() => [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] # {"username-password": "******"} # get_account() => [ # {"username": "******", "password": "******"}, # {"username": "******", "password": "******"} # ] parameter_dict = { key: parameter_item[key] for key in parameter_name_list } elif isinstance(parameter_item, (list, tuple)): # {"username-password": "******"} # get_account() => [("user1", "111111"), ("user2", "222222")] parameter_dict = dict( zip(parameter_name_list, parameter_item)) elif len(parameter_name_list) == 1: # {"user_agent": "${get_user_agent()}"} # get_user_agent() => ["iOS/10.1", "iOS/10.2"] parameter_dict = {parameter_name_list[0]: parameter_item} parameter_content_list.append(parameter_dict) parsed_parameters_list.append(parameter_content_list) return utils.gen_cartesian_product(*parsed_parameters_list)
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 parse_data(content, variables_mapping=None, functions_mapping=None, raise_if_variable_not_found=True): """ parse content with variables mapping Args: content (str/dict/list/numeric/bool/type): content to be parsed variables_mapping (dict): variables mapping. functions_mapping (dict): functions mapping. raise_if_variable_not_found (bool): if set False, exception will not raise when VariableNotFound occurred. Returns: parsed content. Examples: >>> content = { 'request': { 'url': '/api/users/$uid', 'headers': {'token': '$token'} } } >>> variables_mapping = {"uid": 1000, "token": "abcdef"} >>> parse_data(content, variables_mapping) { 'request': { 'url': '/api/users/1000', 'headers': {'token': 'abcdef'} } } """ # TODO: refactor type check if content is None or isinstance(content, (numeric_types, bool, type)): return content if isinstance(content, (list, set, tuple)): return [ parse_data( item, variables_mapping, functions_mapping, raise_if_variable_not_found ) for item in content ] if isinstance(content, dict): parsed_content = {} for key, value in content.items(): parsed_key = parse_data( key, variables_mapping, functions_mapping, raise_if_variable_not_found ) parsed_value = parse_data( value, variables_mapping, functions_mapping, raise_if_variable_not_found ) parsed_content[parsed_key] = parsed_value if extract_variables(parsed_value) == [] and extract_functions(parsed_value) == []: variables_mapping[parsed_key] = parsed_value return parsed_content if isinstance(content, basestring): # content is in string format here variables_mapping = utils.ensure_mapping_format(variables_mapping or {}) functions_mapping = functions_mapping or {} content = content.strip() try: # replace functions with evaluated value # Notice: parse_string_functions must be called before parse_string_variables content = parse_string_functions( content, variables_mapping, functions_mapping ) if extract_functions(content) != []: content = parse_data(content, variables_mapping, functions_mapping) # replace variables with binding value content = parse_string_variables( content, variables_mapping, functions_mapping ) except exceptions.VariableNotFound: if raise_if_variable_not_found: raise return content
def __init__(self, functions, variables=None): self.session_variables_mapping = utils.ensure_mapping_format(variables or {}) self.FUNCTIONS_MAPPING = functions self.init_test_variables() self.validation_results = []