fix merge & override api variables/validate/extract/hooks/etc

This commit is contained in:
httprunner
2018-11-06 23:44:46 +08:00
parent 962f484b75
commit aad7b1972d
3 changed files with 133 additions and 89 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
__title__ = 'HttpRunner' __title__ = 'HttpRunner'
__description__ = 'One-stop solution for HTTP(S) testing.' __description__ = 'One-stop solution for HTTP(S) testing.'
__url__ = 'https://github.com/HttpRunner/HttpRunner' __url__ = 'https://github.com/HttpRunner/HttpRunner'
__version__ = '1.5.15' __version__ = '1.6.0.alpha'
__author__ = 'debugtalk' __author__ = 'debugtalk'
__author_email__ = 'mail@debugtalk.com' __author_email__ = 'mail@debugtalk.com'
__license__ = 'MIT' __license__ = 'MIT'
+102 -65
View File
@@ -1,4 +1,5 @@
import collections import collections
import copy
import csv import csv
import importlib import importlib
import io import io
@@ -10,7 +11,6 @@ import yaml
from httprunner import built_in, exceptions, logger, parser, utils, validator from httprunner import built_in, exceptions, logger, parser, utils, validator
from httprunner.compat import OrderedDict from httprunner.compat import OrderedDict
############################################################################### ###############################################################################
## file loader ## file loader
############################################################################### ###############################################################################
@@ -235,6 +235,7 @@ def load_python_module(module):
} }
""" """
# TODO (2.0): remove variables from debugtalk.py
debugtalk_module = { debugtalk_module = {
"variables": {}, "variables": {},
"functions": {} "functions": {}
@@ -324,46 +325,46 @@ def _load_teststeps(test_block, project_mapping):
{ {
"name": "add product to cart", "name": "add product to cart",
"api": "api_add_cart()", "api": "api_add_cart()",
"validate": [] "variables": [],
"validate": [],
"extract": []
} }
# testcase reference # testcase reference
{ {
"name": "add product to cart", "name": "add product to cart",
"suite": "create_and_check()", "suite": "create_and_check()",
"validate": [] "variables": []
} }
# define directly # define directly
{ {
"name": "checkout cart", "name": "checkout cart",
"request": {}, "request": {},
"validate": [] "variables": [],
"validate": [],
"extract": []
} }
Returns: Returns:
list: loaded teststeps list list: loaded teststeps list
""" """
def extend_api_definition(block):
ref_call = block["api"]
def_block = _get_block_by_name(ref_call, "def-api", project_mapping)
_extend_block(block, def_block)
teststeps = [] teststeps = []
# reference api # reference api
if "api" in test_block: if "api" in test_block:
extend_api_definition(test_block) ref_call = test_block.pop("api")
teststeps.append(test_block) def_block = _get_block_by_name(ref_call, "def-api", project_mapping)
extended_block = _extend_block(test_block, def_block)
teststeps.append(extended_block)
# reference testcase # reference testcase
elif "suite" in test_block: # TODO: replace suite with testcase elif "suite" in test_block: # TODO: replace suite with testcase
ref_call = test_block["suite"] ref_call = test_block.pop("suite")
block = _get_block_by_name(ref_call, "def-testcase", project_mapping) def_block = _get_block_by_name(ref_call, "def-testcase", project_mapping)
# TODO: bugfix lost block config variables # TODO: bugfix lost block config variables
for teststep in block["teststeps"]: for teststep in def_block["teststeps"]:
if "api" in teststep: _teststeps = _load_teststeps(teststep, project_mapping)
extend_api_definition(teststep) teststeps.extend(_teststeps)
teststeps.append(teststep)
# define directly # define directly
else: else:
@@ -491,6 +492,8 @@ def _get_test_definition(name, ref_type, project_mapping):
""" """
block = project_mapping.get(ref_type, {}).get(name) block = project_mapping.get(ref_type, {}).get(name)
# NOTICE: avoid project_mapping been changed during iteration.
block = copy.deepcopy(block)
if not block: if not block:
err_msg = "{} not found!".format(name) err_msg = "{} not found!".format(name)
@@ -504,7 +507,7 @@ def _get_test_definition(name, ref_type, project_mapping):
def _extend_block(ref_block, def_block): def _extend_block(ref_block, def_block):
""" extend ref_block with def_block. """ extend ref_block with def_block, ref_block will merge and override def_block.
Args: Args:
def_block (dict): api definition dict. def_block (dict): api definition dict.
@@ -533,27 +536,59 @@ def _extend_block(ref_block, def_block):
} }
""" """
# TODO: override variables extended_block = {}
def_validators = def_block.get("validate") or def_block.get("validators", [])
ref_validators = ref_block.get("validate") or ref_block.get("validators", [])
def_extrators = def_block.get("extract") \ # override name
or def_block.get("extractors") \ extended_block["name"] = ref_block.pop("name", None) or def_block.pop("name", "")
or def_block.get("extract_binds", [])
ref_extractors = ref_block.get("extract") \
or ref_block.get("extractors") \
or ref_block.get("extract_binds", [])
ref_block.update(def_block) # override variables
ref_block["validate"] = _merge_validator( def_variables = def_block.pop("variables", [])
ref_variables = ref_block.pop("variables", [])
extended_block["variables"] = _extend_variables(
def_variables,
ref_variables
)
# merge & override validators
def_validators = def_block.pop("validate", None) or def_block.pop("validators", [])
ref_validators = ref_block.pop("validate", None) or ref_block.pop("validators", [])
extended_block["validate"] = _extend_validators(
def_validators, def_validators,
ref_validators ref_validators
) )
ref_block["extract"] = _merge_extractor(
# merge & override extractors
def_extrators = def_block.pop("extract", None) \
or def_block.pop("extractors", None) \
or def_block.pop("extract_binds", [])
ref_extractors = ref_block.pop("extract", None) \
or ref_block.pop("extractors", None) \
or ref_block.pop("extract_binds", [])
extended_block["extract"] = _extend_variables(
def_extrators, def_extrators,
ref_extractors ref_extractors
) )
# TODO: merge & override request
def_request = def_block.pop("request", {})
ref_request = ref_block.pop("request", {})
extended_block["request"] = def_request
# merge & override setup_hooks
def_setup_hooks = def_block.pop("setup_hooks", [])
ref_setup_hooks = ref_block.pop("setup_hooks", [])
extended_block["setup_hooks"] = list(set(def_setup_hooks + ref_setup_hooks))
# merge & override teardown_hooks
def_teardown_hooks = def_block.pop("teardown_hooks", [])
ref_teardown_hooks = ref_block.pop("teardown_hooks", [])
extended_block["teardown_hooks"] = list(set(def_teardown_hooks + ref_teardown_hooks))
# TODO: extend with other ref block items, e.g. times
# extended_block.update(def_block)
extended_block.update(ref_block)
return extended_block
def _convert_validators_to_mapping(validators): def _convert_validators_to_mapping(validators):
""" convert validators list to mapping. """ convert validators list to mapping.
@@ -592,20 +627,21 @@ def _convert_validators_to_mapping(validators):
return validators_mapping return validators_mapping
def _merge_validator(def_validators, ref_validators): def _extend_validators(def_validators, ref_validators):
""" merge def_validators with ref_validators. """ extend ref_validators with def_validators.
ref_validators will merge and override def_validators.
Args: Args:
def_validators (list): def_validators (list):
ref_validators (list): ref_validators (list):
Returns: Returns:
list: merged validators list: extended validators
Examples: Examples:
>>> def_validators = [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}] >>> def_validators = [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}]
>>> ref_validators = [{"check": "v1", "expect": 201}, {'len_eq': ['s3', 12]}] >>> ref_validators = [{"check": "v1", "expect": 201}, {'len_eq': ['s3', 12]}]
>>> _merge_validator(def_validators, ref_validators) >>> _extend_validators(def_validators, ref_validators)
[ [
{"check": "v1", "expect": 201, "comparator": "eq"}, {"check": "v1", "expect": 201, "comparator": "eq"},
{"check": "s2", "expect": 16, "comparator": "len_eq"}, {"check": "s2", "expect": 16, "comparator": "len_eq"},
@@ -627,20 +663,21 @@ def _merge_validator(def_validators, ref_validators):
return list(def_validators_mapping.values()) return list(def_validators_mapping.values())
def _merge_extractor(def_extrators, ref_extractors): def _extend_variables(def_variables, ref_variables):
""" merge def_extrators with ref_extractors """ extend ref_variables with def_variables.
ref_variables will merge and override def_variables.
Args: Args:
def_extrators (list): [{"var1": "val1"}, {"var2": "val2"}] def_variables (list):
ref_extractors (list): [{"var1": "val111"}, {"var3": "val3"}] ref_variables (list):
Returns: Returns:
list: merged extractors list: extended variables
Examples: Examples:
>>> def_extrators = [{"var1": "val1"}, {"var2": "val2"}] >>> def_variables = [{"var1": "val1"}, {"var2": "val2"}]
>>> ref_extractors = [{"var1": "val111"}, {"var3": "val3"}] >>> ref_variables = [{"var1": "val111"}, {"var3": "val3"}]
>>> _merge_extractor(def_extrators, ref_extractors) >>> _extend_variables(def_variables, ref_variables)
[ [
{"var1": "val111"}, {"var1": "val111"},
{"var2": "val2"}, {"var2": "val2"},
@@ -648,35 +685,29 @@ def _merge_extractor(def_extrators, ref_extractors):
] ]
""" """
if not def_extrators: if not def_variables:
return ref_extractors return ref_variables
elif not ref_extractors: elif not ref_variables:
return def_extrators return def_variables
else: else:
extractor_dict = OrderedDict() extended_variables_dict = OrderedDict()
for api_extrator in def_extrators: for def_variable in def_variables:
if len(api_extrator) != 1: var_name = list(def_variable.keys())[0]
logger.log_warning("incorrect extractor: {}".format(api_extrator)) extended_variables_dict[var_name] = def_variable[var_name]
for ref_variable in ref_variables:
if not ref_variable:
continue continue
var_name = list(ref_variable.keys())[0]
extended_variables_dict[var_name] = ref_variable[var_name]
var_name = list(api_extrator.keys())[0] extended_variables = []
extractor_dict[var_name] = api_extrator[var_name] for key, value in extended_variables_dict.items():
extended_variables.append({key: value})
for test_extrator in ref_extractors: return extended_variables
if len(test_extrator) != 1:
logger.log_warning("incorrect extractor: {}".format(test_extrator))
continue
var_name = list(test_extrator.keys())[0]
extractor_dict[var_name] = test_extrator[var_name]
extractor_list = []
for key, value in extractor_dict.items():
extractor_list.append({key: value})
return extractor_list
def load_folder_content(folder_path): def load_folder_content(folder_path):
@@ -743,6 +774,7 @@ def load_api_folder(api_folder_path):
} }
""" """
# TODO: refactor api storage format, use one file for each api.
api_definition_mapping = {} api_definition_mapping = {}
api_items_mapping = load_folder_content(api_folder_path) api_items_mapping = load_folder_content(api_folder_path)
@@ -752,6 +784,7 @@ def load_api_folder(api_folder_path):
for api_item in api_items: for api_item in api_items:
key, api_dict = api_item.popitem() key, api_dict = api_item.popitem()
# TODO: replace def with api file path
api_def = api_dict.pop("def") api_def = api_dict.pop("def")
function_meta = parser.parse_function(api_def) function_meta = parser.parse_function(api_def)
func_name = function_meta["func_name"] func_name = function_meta["func_name"]
@@ -826,6 +859,7 @@ def load_test_folder(test_folder_path):
test_definition_mapping[test_file_path] = testcase test_definition_mapping[test_file_path] = testcase
continue continue
# TODO: replace def with testcase file path
testcase_def = block.pop("def") testcase_def = block.pop("def")
function_meta = parser.parse_function(testcase_def) function_meta = parser.parse_function(testcase_def)
func_name = function_meta["func_name"] func_name = function_meta["func_name"]
@@ -837,6 +871,7 @@ def load_test_folder(test_folder_path):
test_definition_mapping[func_name] = testcase test_definition_mapping[func_name] = testcase
else: else:
# key == "test": # key == "test":
### TODO: extend suite with api
testcase["teststeps"].append(block) testcase["teststeps"].append(block)
return test_definition_mapping return test_definition_mapping
@@ -1016,6 +1051,8 @@ def load_locust_tests(path, dot_env_path=None):
project_mapping = load_project_tests(path, dot_env_path) project_mapping = load_project_tests(path, dot_env_path)
config = { config = {
"variables": project_mapping["debugtalk"]["variables"],
"functions": project_mapping["debugtalk"]["functions"],
"refs": project_mapping "refs": project_mapping
} }
tests = [] tests = []
+30 -23
View File
@@ -285,7 +285,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_teststeps(self): def test_load_teststeps(self):
test_block = { test_block = {
"name": "setup and reset all.", "name": "setup and reset all (override).",
"suite": "setup_and_reset($device_sn)", "suite": "setup_and_reset($device_sn)",
"output": ["token", "device_sn"] "output": ["token", "device_sn"]
} }
@@ -322,21 +322,28 @@ class TestSuiteLoader(unittest.TestCase):
) )
test_block = { test_block = {
"name": "override block", "name": "override block",
"times": 3,
"variables": [ "variables": [
{"var": 123} {"var": 123}
], ],
'request': { 'request': {
'url': '/api/get-token', 'method': 'POST', 'headers': {'user_agent': '$user_agent', 'device_sn': '$device_sn', 'os_platform': '$os_platform', 'app_version': '$app_version'}, 'json': {'sign': '${get_sign($user_agent, $device_sn, $os_platform, $app_version)}'}}, 'url': '/api/get-token',
'method': 'POST',
'headers': {'user_agent': '$user_agent', 'device_sn': '$device_sn', 'os_platform': '$os_platform', 'app_version': '$app_version'},
'json': {'sign': '${get_sign($user_agent, $device_sn, $os_platform, $app_version)}'}
},
'validate': [ 'validate': [
{'eq': ['status_code', 201]}, {'eq': ['status_code', 201]},
{'len_eq': ['content.token', 32]} {'len_eq': ['content.token', 32]}
] ]
} }
loader._extend_block(test_block, def_block) extended_block = loader._extend_block(test_block, def_block)
self.assertEqual(test_block["name"], "override block") self.assertEqual(extended_block["name"], "override block")
self.assertIn({'check': 'status_code', 'expect': 201, 'comparator': 'eq'}, test_block["validate"]) self.assertIn({'var': 123}, extended_block["variables"])
self.assertIn({'check': 'content.token', 'comparator': 'len_eq', 'expect': 32}, test_block["validate"]) self.assertIn({'check': 'status_code', 'expect': 201, 'comparator': 'eq'}, extended_block["validate"])
self.assertIn({'check': 'content.token', 'comparator': 'len_eq', 'expect': 32}, extended_block["validate"])
self.assertEqual(extended_block["times"], 3)
def test_get_test_definition_api(self): def test_get_test_definition_api(self):
api_def = loader._get_test_definition("get_headers", "def-api", self.project_mapping) api_def = loader._get_test_definition("get_headers", "def-api", self.project_mapping)
@@ -354,7 +361,7 @@ class TestSuiteLoader(unittest.TestCase):
with self.assertRaises(exceptions.TestcaseNotFound): with self.assertRaises(exceptions.TestcaseNotFound):
loader._get_test_definition("create_and_check_XXX", "def-testcase", self.project_mapping) loader._get_test_definition("create_and_check_XXX", "def-testcase", self.project_mapping)
def test_merge_validator(self): def test_extend_validators(self):
def_validators = [ def_validators = [
{'eq': ['v1', 200]}, {'eq': ['v1', 200]},
{"check": "s2", "expect": 16, "comparator": "len_eq"} {"check": "s2", "expect": 16, "comparator": "len_eq"}
@@ -364,21 +371,21 @@ class TestSuiteLoader(unittest.TestCase):
{'len_eq': ['s3', 12]} {'len_eq': ['s3', 12]}
] ]
merged_validators = loader._merge_validator(def_validators, current_validators) extended_validators = loader._extend_validators(def_validators, current_validators)
self.assertIn( self.assertIn(
{"check": "v1", "expect": 201, "comparator": "eq"}, {"check": "v1", "expect": 201, "comparator": "eq"},
merged_validators extended_validators
) )
self.assertIn( self.assertIn(
{"check": "s2", "expect": 16, "comparator": "len_eq"}, {"check": "s2", "expect": 16, "comparator": "len_eq"},
merged_validators extended_validators
) )
self.assertIn( self.assertIn(
{"check": "s3", "expect": 12, "comparator": "len_eq"}, {"check": "s3", "expect": 12, "comparator": "len_eq"},
merged_validators extended_validators
) )
def test_merge_validator_with_dict(self): def test_extend_validators_with_dict(self):
def_validators = [ def_validators = [
{'eq': ["a", {"v": 1}]}, {'eq': ["a", {"v": 1}]},
{'eq': [{"b": 1}, 200]} {'eq': [{"b": 1}, 200]}
@@ -388,27 +395,27 @@ class TestSuiteLoader(unittest.TestCase):
{'eq': [{"b": 1}, 201]} {'eq': [{"b": 1}, 201]}
] ]
merged_validators = loader._merge_validator(def_validators, current_validators) extended_validators = loader._extend_validators(def_validators, current_validators)
self.assertEqual(len(merged_validators), 3) self.assertEqual(len(extended_validators), 3)
self.assertIn({'check': {'b': 1}, 'expect': 201, 'comparator': 'eq'}, merged_validators) self.assertIn({'check': {'b': 1}, 'expect': 201, 'comparator': 'eq'}, extended_validators)
self.assertNotIn({'check': {'b': 1}, 'expect': 200, 'comparator': 'eq'}, merged_validators) self.assertNotIn({'check': {'b': 1}, 'expect': 200, 'comparator': 'eq'}, extended_validators)
def test_merge_extractor(self): def test_extend_variables(self):
api_extrators = [{"var1": "val1"}, {"var2": "val2"}] def_variables = [{"var1": "val1"}, {"var2": "val2"}]
current_extractors = [{"var1": "val111"}, {"var3": "val3"}] ref_variables = [{"var1": "val111"}, {"var3": "val3"}]
merged_extractors = loader._merge_extractor(api_extrators, current_extractors) extended_variables = loader._extend_variables(def_variables, ref_variables)
self.assertIn( self.assertIn(
{"var1": "val111"}, {"var1": "val111"},
merged_extractors extended_variables
) )
self.assertIn( self.assertIn(
{"var2": "val2"}, {"var2": "val2"},
merged_extractors extended_variables
) )
self.assertIn( self.assertIn(
{"var3": "val3"}, {"var3": "val3"},
merged_extractors extended_variables
) )
def test_load_testcases_by_path_files(self): def test_load_testcases_by_path_files(self):