refactor parameterization:

parameter value now can be in three types:
(1) data list
(2) call built-in parameterize function
(3) call custom function in debugtalk.py
This commit is contained in:
debugtalk
2018-03-07 16:21:51 +08:00
parent 85c49e6924
commit f53baaf757
8 changed files with 124 additions and 51 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
__title__ = 'HttpRunner' __title__ = 'HttpRunner'
__description__ = 'HTTP test runner, not just about api test and load test.' __description__ = 'HTTP test runner, not just about api test and load test.'
__url__ = 'https://github.com/HttpRunner/HttpRunner' __url__ = 'https://github.com/HttpRunner/HttpRunner'
__version__ = '0.9.9' __version__ = '1.0.0'
__author__ = 'debugtalk' __author__ = 'debugtalk'
__author_email__ = 'mail@debugtalk.com' __author_email__ = 'mail@debugtalk.com'
__license__ = 'MIT' __license__ = 'MIT'
+1 -1
View File
@@ -59,7 +59,7 @@ class TestSuite(unittest.TestSuite):
self.config_dict["variables"] = utils.override_variables_binds(variables, variables_mapping) self.config_dict["variables"] = utils.override_variables_binds(variables, variables_mapping)
parameters = self.config_dict.get("parameters", []) parameters = self.config_dict.get("parameters", [])
cartesian_product_parameters = testcase.gen_cartesian_product_parameters( cartesian_product_parameters = testcase.parse_parameters(
parameters, parameters,
self.config_dict["path"] self.config_dict["path"]
) or [{}] ) or [{}]
+57 -26
View File
@@ -11,8 +11,8 @@ import yaml
from httprunner import exception, logger, utils from httprunner import exception, logger, utils
variable_regexp = r"\$([\w_]+)" variable_regexp = r"\$([\w_]+)"
function_regexp = r"\$\{([\w_]+\([\$\w_ =,]*\))\}" function_regexp = r"\$\{([\w_]+\([\$\w\.\-_ =,]*\))\}"
function_regexp_compile = re.compile(r"^([\w_]+)\(([\$\w_ =,]*)\)$") function_regexp_compile = re.compile(r"^([\w_]+)\(([\$\w\.\-_ =,]*)\)$")
test_def_overall_dict = { test_def_overall_dict = {
"loaded": False, "loaded": False,
"api": {}, "api": {},
@@ -73,10 +73,6 @@ def _load_csv_file(csv_file):
if not parameter_list: if not parameter_list:
# first line will always be parameter name # first line will always be parameter name
expected_filename = "{}.csv".format("-".join(line_data))
if not csv_file.endswith(expected_filename):
raise exception.FileFormatError("CSV file name does not match with headers: {}".format(csv_file))
parameter_list = line_data parameter_list = line_data
collums_num = len(parameter_list) collums_num = len(parameter_list)
continue continue
@@ -642,34 +638,54 @@ def gen_cartesian_product(*args):
return product_list return product_list
def gen_cartesian_product_parameters(parameters, testset_path): def parse_parameters(parameters, testset_path=None):
""" parse parameters and generate cartesian product """ parse parameters and generate cartesian product
@params @params
(list) parameters: parameter name and fetch method (list) parameters: parameter name and value in list
parameter value may be in three types:
(1) data list
(2) call built-in parameterize function
(3) call custom function in debugtalk.py
e.g. e.g.
[ [
{"user_agent": "Random"}, {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
{"app_version": "Sequential"} {"username-password": "${parameterize(account.csv)}"},
{"app_version": "${gen_app_version()}"}
] ]
(str) testset_path: testset file path, used for locating csv file (str) testset_path: testset file path, used for locating csv file and debugtalk.py
@return cartesian product in list @return cartesian product in list
""" """
parameters_content_list = [] testcase_parser = TestcaseParser(file_path=testset_path)
parsed_parameters_list = []
for parameter in parameters: for parameter in parameters:
parameter_name, fetch_method = list(parameter.items())[0] parameter_name, parameter_content = list(parameter.items())[0]
parameter_file_path = os.path.join( parameter_name_list = parameter_name.split("-")
os.path.dirname(testset_path),
"{}.csv".format(parameter_name)
)
csv_content_list = load_file(parameter_file_path)
if fetch_method.lower() == "random": if isinstance(parameter_content, list):
random.shuffle(csv_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 = [
dict(zip(parameter_name_list, [parameter_item]))
for parameter_item in parameter_content
]
else:
# (2) & (3)
parsed_parameter_content = testcase_parser.eval_content_with_bindings(parameter_content)
# e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}]
# e.g. [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}]
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
]
parameters_content_list.append(csv_content_list) parsed_parameters_list.append(parameter_content_list)
return gen_cartesian_product(*parameters_content_list)
return gen_cartesian_product(*parsed_parameters_list)
class TestcaseParser(object): class TestcaseParser(object):
@@ -726,19 +742,34 @@ class TestcaseParser(object):
raise exception.ParamsError( raise exception.ParamsError(
"{} is not defined in bind {}s!".format(item_name, item_type)) "{} is not defined in bind {}s!".format(item_name, item_type))
def parameterize(self, csv_file_name, fetch_method="Sequential"):
parameter_file_path = os.path.join(
os.path.dirname(self.file_path),
"{}".format(csv_file_name)
)
csv_content_list = load_file(parameter_file_path)
if fetch_method.lower() == "random":
random.shuffle(csv_content_list)
return csv_content_list
def _eval_content_functions(self, content): def _eval_content_functions(self, content):
functions_list = extract_functions(content) functions_list = extract_functions(content)
for func_content in functions_list: for func_content in functions_list:
function_meta = parse_function(func_content) function_meta = parse_function(func_content)
func_name = function_meta['func_name'] func_name = function_meta['func_name']
func = self.get_bind_item("function", func_name)
args = function_meta.get('args', []) args = function_meta.get('args', [])
kwargs = function_meta.get('kwargs', {}) kwargs = function_meta.get('kwargs', {})
args = self.eval_content_with_bindings(args) args = self.eval_content_with_bindings(args)
kwargs = self.eval_content_with_bindings(kwargs) kwargs = self.eval_content_with_bindings(kwargs)
eval_value = func(*args, **kwargs)
if func_name in ["parameterize", "P"]:
eval_value = self.parameterize(*args, **kwargs)
else:
func = self.get_bind_item("function", func_name)
eval_value = func(*args, **kwargs)
func_content = "${" + func_content + "}" func_content = "${" + func_content + "}"
if func_content == content: if func_content == content:
+12
View File
@@ -48,3 +48,15 @@ def skip_test_in_production_env():
""" skip this test in production environment """ skip this test in production environment
""" """
return os.environ["TEST_ENV"] == "PRODUCTION" return os.environ["TEST_ENV"] == "PRODUCTION"
def gen_app_version():
return [
{"app_version": "2.8.5"},
{"app_version": "2.8.6"}
]
def get_account():
return [
{"username": "user1", "password": "111111"},
{"username": "user2", "password": "222222"}
]
+4 -4
View File
@@ -1,13 +1,13 @@
- config: - config:
name: "user management testset." name: "user management testset."
parameters: parameters:
- user_agent: Random - user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
- app_version: Sequential - app_version: ${gen_app_version()}
- username: ${parameterize(account.csv)}
variables: variables:
- user_agent: 'iOS/10.3' - user_agent: 'iOS/10.3'
- device_sn: ${gen_random_string(15)} - device_sn: ${gen_random_string(15)}
- os_platform: 'ios' - os_platform: 'ios'
- app_version: '2.8.6'
request: request:
base_url: $BASE_URL base_url: $BASE_URL
headers: headers:
@@ -17,7 +17,7 @@
- token - token
- test: - test:
name: get token with $user_agent and $app_version name: get token with $user_agent and $app_version, username $username
api: get_token($user_agent, $device_sn, $os_platform, $app_version) api: get_token($user_agent, $device_sn, $os_platform, $app_version)
extract: extract:
- token: content.token - token: content.token
+1 -1
View File
@@ -164,4 +164,4 @@ class TestRunner(ApiServerUnittest):
result = HttpRunner(testcase_file_path).run() result = HttpRunner(testcase_file_path).run()
self.assertTrue(result["success"]) self.assertTrue(result["success"])
self.assertIn("token", result["output"]) self.assertIn("token", result["output"])
self.assertEqual(result["stat"]["testsRun"], 6) self.assertEqual(result["stat"]["testsRun"], 3 * 2 * 3)
+48 -18
View File
@@ -51,7 +51,7 @@ class TestcaseParserUnittest(unittest.TestCase):
def test_load_csv_file_multiple_parameters(self): def test_load_csv_file_multiple_parameters(self):
csv_file_path = os.path.join( csv_file_path = os.path.join(
os.getcwd(), 'tests/data/username-password.csv') os.getcwd(), 'tests/data/account.csv')
csv_content = testcase.load_file(csv_file_path) csv_content = testcase.load_file(csv_file_path)
self.assertEqual( self.assertEqual(
csv_content, csv_content,
@@ -105,40 +105,70 @@ class TestcaseParserUnittest(unittest.TestCase):
product_list = testcase.gen_cartesian_product(*parameters_content_list) product_list = testcase.gen_cartesian_product(*parameters_content_list)
self.assertEqual(product_list, []) self.assertEqual(product_list, [])
def test_gen_cartesian_product_parameters_one_to_one(self): def test_parse_parameters_raw_list(self):
parameters = [ parameters = [
{"user_agent": "random"}, {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
{"app_version": "sequential"} {"username-password": [("user1", "111111"), ("test2", "222222")]}
] ]
testset_path = os.path.join( cartesian_product_parameters = testcase.parse_parameters(parameters)
os.getcwd(),
"tests/data/demo_parameters.yml"
)
cartesian_product_parameters = testcase.gen_cartesian_product_parameters(
parameters,
testset_path
)
self.assertEqual( self.assertEqual(
len(cartesian_product_parameters), len(cartesian_product_parameters),
6 3 * 2
) )
def test_gen_cartesian_product_parameters_one_to_multiple(self): def test_parse_parameters_parameterize(self):
parameters = [ parameters = [
{"user_agent": "random"}, {"app_version": "${parameterize(app_version.csv)}"},
{"username-password": "sequential"} {"username-password": "${parameterize(account.csv)}"}
] ]
testset_path = os.path.join( testset_path = os.path.join(
os.getcwd(), os.getcwd(),
"tests/data/demo_parameters.yml" "tests/data/demo_parameters.yml"
) )
cartesian_product_parameters = testcase.gen_cartesian_product_parameters( cartesian_product_parameters = testcase.parse_parameters(
parameters, parameters,
testset_path testset_path
) )
self.assertEqual( self.assertEqual(
len(cartesian_product_parameters), len(cartesian_product_parameters),
9 2 * 3
)
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"
)
cartesian_product_parameters = testcase.parse_parameters(
parameters,
testset_path
)
self.assertEqual(
len(cartesian_product_parameters),
2 * 2
)
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 = testcase.parse_parameters(
parameters,
testset_path
)
self.assertEqual(
len(cartesian_product_parameters),
3 * 2 * 3
) )
def test_load_yaml_file_file_format_error(self): def test_load_yaml_file_file_format_error(self):