diff --git a/httprunner/loader.py b/httprunner/loader.py index 16235d0a..d86cb07b 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -898,7 +898,7 @@ def load_project_tests(folder_path): def load_testcases(path): - """ load testcases from file path + """ load testcases from file path, extend and merge with api/testcase definitions. Args: path (str): testcase file/foler path. diff --git a/httprunner/parser.py b/httprunner/parser.py index 5d62032f..defc588d 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -4,7 +4,7 @@ import ast import os import re -from httprunner import exceptions +from httprunner import exceptions, utils from httprunner.compat import basestring, builtin_str, numeric_types, str variable_regexp = r"\$([\w_]+)" @@ -256,6 +256,72 @@ def substitute_variables(content, variables_mapping): return content +def parse_parameters(parameters, variables_mapping, functions_mapping): + """ 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 debugtalk.py + 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": "${parameterize(account.csv)}"}, + {"app_version": "${gen_app_version()}"} + ] + >>> parse_parameters(parameters) + + """ + parsed_parameters_list = [] + for parameter in parameters: + parameter_name, parameter_content = list(parameter.items())[0] + 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": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}] + 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": "user1", "password": "111111"} + parameter_content_dict = dict(zip(parameter_name_list, parameter_item)) + + parameter_content_list.append(parameter_content_dict) + else: + # (2) & (3) + parsed_parameter_content = parse_data(parameter_content, variables_mapping, functions_mapping) + # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] + # e.g. [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}] + if not isinstance(parsed_parameter_content, list): + raise exceptions.ParamsError("parameters syntax error!") + + parameter_content_list = [ + # get subset by parameter name + {key: parameter_item[key] for key in parameter_name_list} + for parameter_item in parsed_parameter_content + ] + + parsed_parameters_list.append(parameter_content_list) + + return utils.gen_cartesian_product(*parsed_parameters_list) + + ############################################################################### ## parse content with variables and functions mapping ############################################################################### @@ -337,8 +403,13 @@ def parse_string_functions(content, variables_mapping, functions_mapping): args = parse_data(args, variables_mapping, functions_mapping) kwargs = parse_data(kwargs, variables_mapping, functions_mapping) - func = get_mapping_function(func_name, functions_mapping) - eval_value = func(*args, **kwargs) + if func_name in ["parameterize", "P"]: + # TODO: add parameterize + # eval_value = load_csv_list(*args, **kwargs) + pass + else: + func = get_mapping_function(func_name, functions_mapping) + eval_value = func(*args, **kwargs) func_content = "${" + func_content + "}" if func_content == content: diff --git a/tests/test_parser.py b/tests/test_parser.py index 8613db61..c0005284 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -2,7 +2,7 @@ import os import time import unittest -from httprunner import exceptions, parser +from httprunner import exceptions, loader, parser class TestParser(unittest.TestCase): @@ -356,3 +356,74 @@ class TestParser(unittest.TestCase): substituted_data = parser.substitute_variables(content, variables_mapping) self.assertEqual(substituted_data["request"]["url"], "/api/users/1000") self.assertEqual(substituted_data["request"]["headers"], {'token': '$token'}) + + def test_parse_parameters_raw_list(self): + parameters = [ + {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, + {"username-password": [("user1", "111111"), ["test2", "222222"]]} + ] + variables_mapping = {} + functions_mapping = {} + cartesian_product_parameters = parser.parse_parameters( + parameters, variables_mapping, functions_mapping) + self.assertEqual( + len(cartesian_product_parameters), + 3 * 2 + ) + self.assertEqual( + cartesian_product_parameters[0], + {'user_agent': 'iOS/10.1', 'username': 'user1', 'password': '111111'} + ) + + def test_parse_parameters_custom_function(self): + parameters = [ + {"app_version": "${gen_app_version()}"}, + {"username-password": "${get_account()}"} + ] + testset_path = os.path.join( + os.getcwd(), + "tests/data/demo_parameters.yml" + ) + variables_mapping = {} + functions_mapping = {} + from tests import debugtalk + debugtalk_module = loader.load_python_module(debugtalk) + cartesian_product_parameters = parser.parse_parameters( + parameters, + debugtalk_module["variables"], + debugtalk_module["functions"] + ) + self.assertEqual( + len(cartesian_product_parameters), + 2 * 2 + ) + + # def test_parse_parameters_parameterize(self): + # parameters = [ + # {"app_version": "${parameterize(app_version.csv)}"}, + # {"username-password": "${parameterize(account.csv)}"} + # ] + + # cartesian_product_parameters = parser.parse_parameters( + # parameters, variables_mapping, functions_mapping) + # self.assertEqual( + # len(cartesian_product_parameters), + # 2 * 3 + # ) + + # def test_parse_parameters_mix(self): + # parameters = [ + # {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, + # {"app_version": "${gen_app_version()}"}, + # {"username-password": "${parameterize(account.csv)}"} + # ] + # testset_path = os.path.join( + # os.getcwd(), + # "tests/data/demo_parameters.yml" + # ) + # cartesian_product_parameters = parser.parse_parameters( + # parameters, variables_mapping, functions_mapping) + # self.assertEqual( + # len(cartesian_product_parameters), + # 3 * 2 * 3 + # )