refactor context:

1, testset and testcase are in different context level;
2, testset context will be initialized when a file is loaded, and testcase level context initializes when each testcase starts;
3, testcase context should inherit from testset context configs, and the testcase context has high priority.
This commit is contained in:
debugtalk
2017-07-01 21:40:46 +08:00
parent da445df78b
commit dc7a4b69bc
6 changed files with 164 additions and 117 deletions

View File

@@ -1,23 +1,45 @@
import copy
import importlib
import re
import types
from collections import OrderedDict
from ate import exception, utils
from ate import exception, testcase, utils
def is_function(tup):
"""
Takes (name, object) tuple, returns True if it is a function.
""" Takes (name, object) tuple, returns True if it is a function.
"""
name, item = tup
return isinstance(item, types.FunctionType)
class Context(object):
""" Manages binding of variables
""" Manages context functions and variables.
context has two levels, testset and testcase.
"""
def __init__(self):
self.functions = dict()
self.variables = dict() # Maps variable name to value
self.testset_config = {}
self.testset_shared_variables_mapping = dict()
self.testcase_config = {}
self.testcase_variables_mapping = dict()
self.init_context()
def init_context(self, level='testset'):
"""
testset level context initializes when a file is loaded,
testcase level context initializes when each testcase starts.
"""
if level == "testset":
self.testset_config["functions"] = {}
self.testset_config["variables"] = OrderedDict()
self.testset_config["request"] = {}
self.testset_shared_variables_mapping = {}
self.testcase_config["functions"] = {}
self.testcase_config["variables"] = OrderedDict()
self.testcase_config["request"] = {}
self.testcase_variables_mapping = copy.deepcopy(self.testset_shared_variables_mapping)
def import_requires(self, modules):
""" import required modules dynamicly
@@ -25,54 +47,111 @@ class Context(object):
for module_name in modules:
globals()[module_name] = importlib.import_module(module_name)
def bind_functions(self, function_binds):
def bind_functions(self, function_binds, level="testcase"):
""" Bind named functions within the context
This allows for passing in self-defined functions in testing.
e.g. function_binds:
{
"add_one": lambda x: x + 1,
"add_two_nums": "lambda x, y: x + y"
"add_one": lambda x: x + 1, # lambda function
"add_two_nums": "lambda x, y: x + y" # lambda function in string
}
"""
eval_function_binds = {}
for func_name, function in function_binds.items():
if isinstance(function, str):
function = eval(function)
self.functions[func_name] = function
eval_function_binds[func_name] = function
def import_module_functions(self, modules):
self.__update_context_config(level, "functions", eval_function_binds)
def import_module_functions(self, modules, level="testcase"):
""" import modules and bind all functions within the context
"""
for module_name in modules:
imported = importlib.import_module(module_name)
imported_functions_dict = dict(filter(is_function, vars(imported).items()))
self.functions.update(imported_functions_dict)
self.__update_context_config(level, "functions", imported_functions_dict)
def bind_variables(self, variable_binds):
""" Bind named variables to value within the context.
This allows for passing in variables or functions.
e.g. variable_binds:
def register_variables_config(self, variable_binds, level="testcase"):
""" register variable configs
@param (list) variable_binds, variable can be value or custom function
e.g.
[
{"TOKEN": "debugtalk"},
{"random": {"func": "gen_random_string", "args": [5]}},
{"json": {'name': 'user', 'password': '123456'}},
{"md5": {"func": "gen_md5", "args": ["$TOKEN", "$json", "$random"]}}
{"md5": {"func": "gen_md5", "args": ["${TOKEN}", "${json}", "${random}"]}}
]
"""
for variable_bind_map in variable_binds:
for var_name, var_value in variable_bind_map.items():
self.variables[var_name] = self.get_eval_value(var_value)
if level == "testset":
for variable_bind in variable_binds:
self.testset_config["variables"].update(variable_bind)
elif level == "testcase":
self.testcase_config["variables"] = copy.deepcopy(self.testset_config["variables"])
for variable_bind in variable_binds:
self.testcase_config["variables"].update(variable_bind)
def update_variables(self, variables_mapping):
""" update context variables binds with new variables mapping
def register_request(self, request_dict, level="testcase"):
self.__update_context_config(level, "request", request_dict)
def __update_context_config(self, level, config_type, config_mapping):
"""
self.variables.update(variables_mapping)
@param level: testset or testcase
@param config_type: functions, variables or request
@param config_mapping: functions config mapping or variables config mapping
"""
if level == "testset":
self.testset_config[config_type].update(config_mapping)
elif level == "testcase":
self.testcase_config[config_type].update(config_mapping)
def get_parsed_request(self):
""" get parsed request, with each variable replaced by bind value.
testcase request shall inherit from testset request configs,
but can not change testset configs, that's why we use copy.deepcopy here.
"""
testcase_request_config = copy.deepcopy(self.testset_config["request"])
testcase_request_config.update(self.testcase_config["request"])
parsed_request = testcase.parse_template(
testcase_request_config,
self._get_evaluated_testcase_variables()
)
return parsed_request
def bind_extracted_variables(self, variables_mapping):
""" bind extracted variable to current testcase context and testset context.
since extracted variable maybe used in current testcase and next testcases.
"""
self.testset_shared_variables_mapping.update(variables_mapping)
self.testcase_variables_mapping.update(variables_mapping)
def get_testcase_variables_mapping(self):
return self.testcase_variables_mapping
def _get_evaluated_testcase_variables(self):
""" variables in variables_config will be evaluated each time
"""
testcase_functions_config = copy.deepcopy(self.testset_config["functions"])
testcase_functions_config.update(self.testcase_config["functions"])
self.testcase_config["functions"] = testcase_functions_config
testcase_variables_config = copy.deepcopy(self.testset_config["variables"])
testcase_variables_config.update(self.testcase_config["variables"])
self.testcase_config["variables"] = testcase_variables_config
for var_name, var_value in self.testcase_config["variables"].items():
self.testcase_variables_mapping[var_name] = self.get_eval_value(var_value)
return self.testcase_variables_mapping
def get_eval_value(self, data):
""" evaluate data recursively, each variable in data will be evaluated.
variables marker: ${variable}.
"""
if isinstance(data, str):
return utils.parse_content_with_variables(data, self.variables)
return utils.parse_content_with_variables(data, self.testcase_variables_mapping)
if isinstance(data, list):
return [self.get_eval_value(item) for item in data]
@@ -85,7 +164,7 @@ class Context(object):
func_name = data['func']
args = self.get_eval_value(data.get('args', []))
kargs = self.get_eval_value(data.get('kargs', {}))
return self.functions[func_name](*args, **kargs)
return self.testcase_config["functions"][func_name](*args, **kargs)
else:
evaluated_data = {}
for key, value in data.items():

View File

@@ -26,7 +26,7 @@ def create_suite(testset):
test_runner = runner.TestRunner()
config_dict = testset.get("config", {})
test_runner.update_context(config_dict, level="testset")
test_runner.init_context(config_dict, level="testset")
testcases = testset.get("testcases", [])
for testcase in testcases:

View File

@@ -1,9 +1,7 @@
import copy
import requests
from ate import exception, response
from ate.context import Context
from ate.testcase import parse_template
class TestRunner(object):
@@ -11,9 +9,8 @@ class TestRunner(object):
def __init__(self):
self.client = requests.Session()
self.context = Context()
self.testset_req_overall_configs = {}
def update_context(self, config_dict, level="testcase"):
def init_context(self, config_dict, level):
""" create/update context variables binds
@param (dict) config_dict
{
@@ -27,28 +24,28 @@ class TestRunner(object):
"lambda *str_args: hashlib.md5(''.join(str_args).\
encode('utf-8')).hexdigest()"
},
"import_module_functions": ["test.data.custom_functions"],
"variable_binds": [
{"TOKEN": "debugtalk"},
{"random": {"func": "gen_random_string", "args": [5]}},
]
}
@param (str) context level, testcase or testset
only when level is testset, shall we update testset_req_overall_configs
"""
requires = config_dict.get('requires', [])
self.context.import_requires(requires)
function_binds = config_dict.get('function_binds', {})
self.context.bind_functions(function_binds)
self.context.bind_functions(function_binds, level)
module_functions = config_dict.get('import_module_functions', [])
self.context.import_module_functions(module_functions)
self.context.import_module_functions(module_functions, level)
variable_binds = config_dict.get('variable_binds', [])
self.context.bind_variables(variable_binds)
self.context.register_variables_config(variable_binds, level)
if level == "testset":
self.testset_req_overall_configs = config_dict.get('request', {})
request_config = config_dict.get('request', {})
self.context.register_request(request_config, level)
def run_test(self, testcase):
""" run single testcase.
@@ -68,21 +65,14 @@ class TestRunner(object):
},
"body": '{"name": "user", "password": "123456"}'
},
"extract_binds": {},
"validators": []
"extract_binds": {}, # optional
"validators": [] # optional
}
@return (tuple) test result of single testcase
(success, diff_content_list)
"""
self.update_context(testcase)
# each testcase shall inherit from testset request configs,
# but can not override testset configs,
# that's why we use copy.deepcopy here.
testcase_request = copy.deepcopy(self.testset_req_overall_configs)
testcase_request.update(testcase["request"])
parsed_request = parse_template(testcase_request, self.context.variables)
self.init_context(testcase, level="testcase")
parsed_request = self.context.get_parsed_request()
try:
url = parsed_request.pop('url')
method = parsed_request.pop('method')
@@ -100,10 +90,11 @@ class TestRunner(object):
extract_binds = testcase.get("extract_binds", {})
extracted_variables_mapping = resp_obj.extract_response(extract_binds)
self.context.update_variables(extracted_variables_mapping)
self.context.bind_extracted_variables(extracted_variables_mapping)
validators = testcase.get("validators", [])
diff_content_list = resp_obj.validate(validators, self.context.variables)
diff_content_list = resp_obj.validate(
validators, self.context.get_testcase_variables_mapping())
return resp_obj.success, diff_content_list
@@ -122,10 +113,10 @@ class TestRunner(object):
"testcases": [
{
"name": "testcase description",
"variable_binds": {}, # override
"variable_binds": {}, # optional, override
"request": {},
"extract_binds": {},
"validators": {}
"extract_binds": {}, # optional
"validators": {} # optional
},
testcase12
]
@@ -139,7 +130,7 @@ class TestRunner(object):
results = []
config_dict = testset.get("config", {})
self.update_context(config_dict)
self.init_context(config_dict, level="testset")
testcases = testset.get("testcases", [])
for testcase in testcases:
result = self.run_test(testcase)

View File

@@ -1,21 +1,15 @@
-
register_variables:
variable_binds:
- TOKEN: "debugtalk"
-
variable_binds:
- var: [1, 2, 3]
-
variable_binds:
- data: {'name': 'user', 'password': '123456'}
-
register_template_variables:
variable_binds:
- TOKEN: "debugtalk"
- token: ${TOKEN}
-
bind_lambda_functions:
function_binds:
add_one: "lambda x: x + 1"
add_two_nums: "lambda x, y: x + y"
@@ -23,7 +17,7 @@
- add1: {"func": "add_one", "args": [2]}
- sum2nums: {"func": "add_two_nums", "args": [2, 3]}
-
bind_lambda_functions_with_import:
requires:
- random
- string

View File

@@ -9,8 +9,8 @@
gen_md5: "lambda *str_args: hashlib.md5(''.join(str_args).encode('utf-8')).hexdigest()"
variable_binds:
- TOKEN: debugtalk
- data: ""
- random: {"func": "gen_random_string", "args": [5]}
- data: '{"name": "user", "password": "123456"}'
- authorization: {"func": "gen_md5", "args": ["${TOKEN}", "${data}", "${random}"]}
- test:

View File

@@ -12,79 +12,53 @@ class VariableBindsUnittest(unittest.TestCase):
testcase_file_path = os.path.join(os.getcwd(), 'test/data/demo_binds.yml')
self.testcases = utils.load_testcases(testcase_file_path)
def test_context_variable_string(self):
def test_context_register_variables(self):
# testcase in JSON format
testcase1 = {
"variable_binds": [
{"TOKEN": "debugtalk"}
]
}
# testcase in YAML format
testcase2 = self.testcases[0]
for testcase in [testcase1, testcase2]:
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
context_variables = self.context.variables
self.assertIn("TOKEN", context_variables)
self.assertEqual(context_variables["TOKEN"], "debugtalk")
def test_context_variable_list(self):
testcase1 = {
"variable_binds": [
{"var": [1, 2, 3]}
]
}
testcase2 = self.testcases[1]
for testcase in [testcase1, testcase2]:
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
context_variables = self.context.variables
self.assertIn("var", context_variables)
self.assertEqual(context_variables["var"], [1, 2, 3])
def test_context_variable_json(self):
testcase1 = {
"variable_binds": [
{"TOKEN": "debugtalk"},
{"var": [1, 2, 3]},
{"data": {'name': 'user', 'password': '123456'}}
]
}
testcase2 = self.testcases[2]
# testcase in YAML format
testcase2 = self.testcases["register_variables"]
for testcase in [testcase1, testcase2]:
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
self.context.register_variables_config(variable_binds)
context_variables = self.context.variables
context_variables = self.context._get_evaluated_testcase_variables()
self.assertIn("TOKEN", context_variables)
self.assertEqual(context_variables["TOKEN"], "debugtalk")
self.assertIn("var", context_variables)
self.assertEqual(context_variables["var"], [1, 2, 3])
self.assertIn("data", context_variables)
self.assertEqual(
context_variables["data"],
{'name': 'user', 'password': '123456'}
)
def test_context_variable_variable(self):
def test_context_register_template_variables(self):
testcase1 = {
"variable_binds": [
{"GLOBAL_TOKEN": "debugtalk"},
{"token": "${GLOBAL_TOKEN}"}
]
}
testcase2 = self.testcases[3]
testcase2 = self.testcases["register_template_variables"]
for testcase in [testcase1, testcase2]:
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
self.context.register_variables_config(variable_binds)
context_variables = self.context.variables
context_variables = self.context._get_evaluated_testcase_variables()
self.assertIn("GLOBAL_TOKEN", context_variables)
self.assertEqual(context_variables["GLOBAL_TOKEN"], "debugtalk")
self.assertIn("token", context_variables)
self.assertEqual(context_variables["token"], "debugtalk")
def test_context_variable_function_lambda(self):
def test_context_bind_lambda_functions(self):
testcase1 = {
"function_binds": {
"add_one": lambda x: x + 1,
@@ -95,22 +69,22 @@ class VariableBindsUnittest(unittest.TestCase):
{"sum2nums": {"func": "add_two_nums", "args": [2, 3]}}
]
}
testcase2 = self.testcases[4]
testcase2 = self.testcases["bind_lambda_functions"]
for testcase in [testcase1, testcase2]:
function_binds = testcase.get('function_binds', {})
self.context.bind_functions(function_binds)
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
self.context.register_variables_config(variable_binds)
context_variables = self.context.variables
context_variables = self.context._get_evaluated_testcase_variables()
self.assertIn("add1", context_variables)
self.assertEqual(context_variables["add1"], 3)
self.assertIn("sum2nums", context_variables)
self.assertEqual(context_variables["sum2nums"], 5)
def test_context_variable_function_lambda_with_import(self):
def test_context_bind_lambda_functions_with_import(self):
testcase1 = {
"requires": ["random", "string", "hashlib"],
"function_binds": {
@@ -120,11 +94,11 @@ class VariableBindsUnittest(unittest.TestCase):
"variable_binds": [
{"TOKEN": "debugtalk"},
{"random": {"func": "gen_random_string", "args": [5]}},
{"data": "{'name': 'user', 'password': '123456'}"},
{"md5": {"func": "gen_md5", "args": ["$TOKEN", "$data", "$random"]}}
{"data": '{"name": "user", "password": "123456"}'},
{"authorization": {"func": "gen_md5", "args": ["${TOKEN}", "${data}", "${random}"]}}
]
}
testcase2 = self.testcases[5]
testcase2 = self.testcases["bind_lambda_functions_with_import"]
for testcase in [testcase1, testcase2]:
requires = testcase.get('requires', [])
@@ -134,11 +108,20 @@ class VariableBindsUnittest(unittest.TestCase):
self.context.bind_functions(function_binds)
variable_binds = testcase['variable_binds']
self.context.bind_variables(variable_binds)
self.context.register_variables_config(variable_binds)
context_variables = self.context._get_evaluated_testcase_variables()
context_variables = self.context.variables
self.assertIn("TOKEN", context_variables)
TOKEN = context_variables["TOKEN"]
self.assertEqual(TOKEN, "debugtalk")
self.assertIn("random", context_variables)
self.assertIsInstance(context_variables["random"], str)
self.assertEqual(len(context_variables["random"]), 5)
self.assertIn("md5", context_variables)
self.assertEqual(len(context_variables["md5"]), 32)
random = context_variables["random"]
self.assertIn("data", context_variables)
data = context_variables["data"]
self.assertIn("authorization", context_variables)
self.assertEqual(len(context_variables["authorization"]), 32)
authorization = context_variables["authorization"]
self.assertEqual(utils.gen_md5(TOKEN, data, random), authorization)