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

This commit is contained in:
debugtalk
2018-11-06 23:44:46 +08:00
parent 9b25bd65fa
commit 547e22a6a5
3 changed files with 133 additions and 89 deletions

View File

@@ -1,7 +1,7 @@
__title__ = 'HttpRunner'
__description__ = 'One-stop solution for HTTP(S) testing.'
__url__ = 'https://github.com/HttpRunner/HttpRunner'
__version__ = '1.5.15'
__version__ = '1.6.0.alpha'
__author__ = 'debugtalk'
__author_email__ = 'mail@debugtalk.com'
__license__ = 'MIT'

View File

@@ -1,4 +1,5 @@
import collections
import copy
import csv
import importlib
import io
@@ -10,7 +11,6 @@ import yaml
from httprunner import built_in, exceptions, logger, parser, utils, validator
from httprunner.compat import OrderedDict
###############################################################################
## file loader
###############################################################################
@@ -235,6 +235,7 @@ def load_python_module(module):
}
"""
# TODO (2.0): remove variables from debugtalk.py
debugtalk_module = {
"variables": {},
"functions": {}
@@ -324,46 +325,46 @@ def _load_teststeps(test_block, project_mapping):
{
"name": "add product to cart",
"api": "api_add_cart()",
"validate": []
"variables": [],
"validate": [],
"extract": []
}
# testcase reference
{
"name": "add product to cart",
"suite": "create_and_check()",
"validate": []
"variables": []
}
# define directly
{
"name": "checkout cart",
"request": {},
"validate": []
"variables": [],
"validate": [],
"extract": []
}
Returns:
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 = []
# reference api
if "api" in test_block:
extend_api_definition(test_block)
teststeps.append(test_block)
ref_call = test_block.pop("api")
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
elif "suite" in test_block: # TODO: replace suite with testcase
ref_call = test_block["suite"]
block = _get_block_by_name(ref_call, "def-testcase", project_mapping)
ref_call = test_block.pop("suite")
def_block = _get_block_by_name(ref_call, "def-testcase", project_mapping)
# TODO: bugfix lost block config variables
for teststep in block["teststeps"]:
if "api" in teststep:
extend_api_definition(teststep)
teststeps.append(teststep)
for teststep in def_block["teststeps"]:
_teststeps = _load_teststeps(teststep, project_mapping)
teststeps.extend(_teststeps)
# define directly
else:
@@ -491,6 +492,8 @@ def _get_test_definition(name, ref_type, project_mapping):
"""
block = project_mapping.get(ref_type, {}).get(name)
# NOTICE: avoid project_mapping been changed during iteration.
block = copy.deepcopy(block)
if not block:
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):
""" extend ref_block with def_block.
""" extend ref_block with def_block, ref_block will merge and override def_block.
Args:
def_block (dict): api definition dict.
@@ -533,27 +536,59 @@ def _extend_block(ref_block, def_block):
}
"""
# TODO: override variables
def_validators = def_block.get("validate") or def_block.get("validators", [])
ref_validators = ref_block.get("validate") or ref_block.get("validators", [])
extended_block = {}
def_extrators = def_block.get("extract") \
or def_block.get("extractors") \
or def_block.get("extract_binds", [])
ref_extractors = ref_block.get("extract") \
or ref_block.get("extractors") \
or ref_block.get("extract_binds", [])
# override name
extended_block["name"] = ref_block.pop("name", None) or def_block.pop("name", "")
ref_block.update(def_block)
ref_block["validate"] = _merge_validator(
# override variables
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,
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,
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):
""" convert validators list to mapping.
@@ -592,20 +627,21 @@ def _convert_validators_to_mapping(validators):
return validators_mapping
def _merge_validator(def_validators, ref_validators):
""" merge def_validators with ref_validators.
def _extend_validators(def_validators, ref_validators):
""" extend ref_validators with def_validators.
ref_validators will merge and override def_validators.
Args:
def_validators (list):
ref_validators (list):
Returns:
list: merged validators
list: extended validators
Examples:
>>> def_validators = [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}]
>>> 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": "s2", "expect": 16, "comparator": "len_eq"},
@@ -627,20 +663,21 @@ def _merge_validator(def_validators, ref_validators):
return list(def_validators_mapping.values())
def _merge_extractor(def_extrators, ref_extractors):
""" merge def_extrators with ref_extractors
def _extend_variables(def_variables, ref_variables):
""" extend ref_variables with def_variables.
ref_variables will merge and override def_variables.
Args:
def_extrators (list): [{"var1": "val1"}, {"var2": "val2"}]
ref_extractors (list): [{"var1": "val111"}, {"var3": "val3"}]
def_variables (list):
ref_variables (list):
Returns:
list: merged extractors
list: extended variables
Examples:
>>> def_extrators = [{"var1": "val1"}, {"var2": "val2"}]
>>> ref_extractors = [{"var1": "val111"}, {"var3": "val3"}]
>>> _merge_extractor(def_extrators, ref_extractors)
>>> def_variables = [{"var1": "val1"}, {"var2": "val2"}]
>>> ref_variables = [{"var1": "val111"}, {"var3": "val3"}]
>>> _extend_variables(def_variables, ref_variables)
[
{"var1": "val111"},
{"var2": "val2"},
@@ -648,35 +685,29 @@ def _merge_extractor(def_extrators, ref_extractors):
]
"""
if not def_extrators:
return ref_extractors
if not def_variables:
return ref_variables
elif not ref_extractors:
return def_extrators
elif not ref_variables:
return def_variables
else:
extractor_dict = OrderedDict()
for api_extrator in def_extrators:
if len(api_extrator) != 1:
logger.log_warning("incorrect extractor: {}".format(api_extrator))
extended_variables_dict = OrderedDict()
for def_variable in def_variables:
var_name = list(def_variable.keys())[0]
extended_variables_dict[var_name] = def_variable[var_name]
for ref_variable in ref_variables:
if not ref_variable:
continue
var_name = list(ref_variable.keys())[0]
extended_variables_dict[var_name] = ref_variable[var_name]
var_name = list(api_extrator.keys())[0]
extractor_dict[var_name] = api_extrator[var_name]
extended_variables = []
for key, value in extended_variables_dict.items():
extended_variables.append({key: value})
for test_extrator in ref_extractors:
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
return extended_variables
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_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:
key, api_dict = api_item.popitem()
# TODO: replace def with api file path
api_def = api_dict.pop("def")
function_meta = parser.parse_function(api_def)
func_name = function_meta["func_name"]
@@ -826,6 +859,7 @@ def load_test_folder(test_folder_path):
test_definition_mapping[test_file_path] = testcase
continue
# TODO: replace def with testcase file path
testcase_def = block.pop("def")
function_meta = parser.parse_function(testcase_def)
func_name = function_meta["func_name"]
@@ -837,6 +871,7 @@ def load_test_folder(test_folder_path):
test_definition_mapping[func_name] = testcase
else:
# key == "test":
### TODO: extend suite with api
testcase["teststeps"].append(block)
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)
config = {
"variables": project_mapping["debugtalk"]["variables"],
"functions": project_mapping["debugtalk"]["functions"],
"refs": project_mapping
}
tests = []

View File

@@ -285,7 +285,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_teststeps(self):
test_block = {
"name": "setup and reset all.",
"name": "setup and reset all (override).",
"suite": "setup_and_reset($device_sn)",
"output": ["token", "device_sn"]
}
@@ -322,21 +322,28 @@ class TestSuiteLoader(unittest.TestCase):
)
test_block = {
"name": "override block",
"times": 3,
"variables": [
{"var": 123}
],
'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': [
{'eq': ['status_code', 201]},
{'len_eq': ['content.token', 32]}
]
}
loader._extend_block(test_block, def_block)
self.assertEqual(test_block["name"], "override block")
self.assertIn({'check': 'status_code', 'expect': 201, 'comparator': 'eq'}, test_block["validate"])
self.assertIn({'check': 'content.token', 'comparator': 'len_eq', 'expect': 32}, test_block["validate"])
extended_block = loader._extend_block(test_block, def_block)
self.assertEqual(extended_block["name"], "override block")
self.assertIn({'var': 123}, extended_block["variables"])
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):
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):
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 = [
{'eq': ['v1', 200]},
{"check": "s2", "expect": 16, "comparator": "len_eq"}
@@ -364,21 +371,21 @@ class TestSuiteLoader(unittest.TestCase):
{'len_eq': ['s3', 12]}
]
merged_validators = loader._merge_validator(def_validators, current_validators)
extended_validators = loader._extend_validators(def_validators, current_validators)
self.assertIn(
{"check": "v1", "expect": 201, "comparator": "eq"},
merged_validators
extended_validators
)
self.assertIn(
{"check": "s2", "expect": 16, "comparator": "len_eq"},
merged_validators
extended_validators
)
self.assertIn(
{"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 = [
{'eq': ["a", {"v": 1}]},
{'eq': [{"b": 1}, 200]}
@@ -388,27 +395,27 @@ class TestSuiteLoader(unittest.TestCase):
{'eq': [{"b": 1}, 201]}
]
merged_validators = loader._merge_validator(def_validators, current_validators)
self.assertEqual(len(merged_validators), 3)
self.assertIn({'check': {'b': 1}, 'expect': 201, 'comparator': 'eq'}, merged_validators)
self.assertNotIn({'check': {'b': 1}, 'expect': 200, 'comparator': 'eq'}, merged_validators)
extended_validators = loader._extend_validators(def_validators, current_validators)
self.assertEqual(len(extended_validators), 3)
self.assertIn({'check': {'b': 1}, 'expect': 201, 'comparator': 'eq'}, extended_validators)
self.assertNotIn({'check': {'b': 1}, 'expect': 200, 'comparator': 'eq'}, extended_validators)
def test_merge_extractor(self):
api_extrators = [{"var1": "val1"}, {"var2": "val2"}]
current_extractors = [{"var1": "val111"}, {"var3": "val3"}]
def test_extend_variables(self):
def_variables = [{"var1": "val1"}, {"var2": "val2"}]
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(
{"var1": "val111"},
merged_extractors
extended_variables
)
self.assertIn(
{"var2": "val2"},
merged_extractors
extended_variables
)
self.assertIn(
{"var3": "val3"},
merged_extractors
extended_variables
)
def test_load_testcases_by_path_files(self):