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 importlib
import re import re
import types import types
from collections import OrderedDict
from ate import exception, utils from ate import exception, testcase, utils
def is_function(tup): 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 name, item = tup
return isinstance(item, types.FunctionType) return isinstance(item, types.FunctionType)
class Context(object): class Context(object):
""" Manages binding of variables """ Manages context functions and variables.
context has two levels, testset and testcase.
""" """
def __init__(self): def __init__(self):
self.functions = dict() self.testset_config = {}
self.variables = dict() # Maps variable name to value 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): def import_requires(self, modules):
""" import required modules dynamicly """ import required modules dynamicly
@@ -25,54 +47,111 @@ class Context(object):
for module_name in modules: for module_name in modules:
globals()[module_name] = importlib.import_module(module_name) 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 """ Bind named functions within the context
This allows for passing in self-defined functions in testing. This allows for passing in self-defined functions in testing.
e.g. function_binds: e.g. function_binds:
{ {
"add_one": lambda x: x + 1, "add_one": lambda x: x + 1, # lambda function
"add_two_nums": "lambda x, y: x + y" "add_two_nums": "lambda x, y: x + y" # lambda function in string
} }
""" """
eval_function_binds = {}
for func_name, function in function_binds.items(): for func_name, function in function_binds.items():
if isinstance(function, str): if isinstance(function, str):
function = eval(function) 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 """ import modules and bind all functions within the context
""" """
for module_name in modules: for module_name in modules:
imported = importlib.import_module(module_name) imported = importlib.import_module(module_name)
imported_functions_dict = dict(filter(is_function, vars(imported).items())) 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): def register_variables_config(self, variable_binds, level="testcase"):
""" Bind named variables to value within the context. """ register variable configs
This allows for passing in variables or functions. @param (list) variable_binds, variable can be value or custom function
e.g. variable_binds: e.g.
[ [
{"TOKEN": "debugtalk"}, {"TOKEN": "debugtalk"},
{"random": {"func": "gen_random_string", "args": [5]}}, {"random": {"func": "gen_random_string", "args": [5]}},
{"json": {'name': 'user', 'password': '123456'}}, {"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: if level == "testset":
for var_name, var_value in variable_bind_map.items(): for variable_bind in variable_binds:
self.variables[var_name] = self.get_eval_value(var_value) 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): def register_request(self, request_dict, level="testcase"):
""" update context variables binds with new variables mapping 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): def get_eval_value(self, data):
""" evaluate data recursively, each variable in data will be evaluated. """ evaluate data recursively, each variable in data will be evaluated.
variables marker: ${variable}. variables marker: ${variable}.
""" """
if isinstance(data, str): 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): if isinstance(data, list):
return [self.get_eval_value(item) for item in data] return [self.get_eval_value(item) for item in data]
@@ -85,7 +164,7 @@ class Context(object):
func_name = data['func'] func_name = data['func']
args = self.get_eval_value(data.get('args', [])) args = self.get_eval_value(data.get('args', []))
kargs = self.get_eval_value(data.get('kargs', {})) 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: else:
evaluated_data = {} evaluated_data = {}
for key, value in data.items(): for key, value in data.items():

View File

@@ -26,7 +26,7 @@ def create_suite(testset):
test_runner = runner.TestRunner() test_runner = runner.TestRunner()
config_dict = testset.get("config", {}) 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", []) testcases = testset.get("testcases", [])
for testcase in testcases: for testcase in testcases:

View File

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

View File

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

View File

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

View File

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