refactor loader: load testcases

This commit is contained in:
debugtalk
2018-08-12 01:25:28 +08:00
parent 391268b4c9
commit 9b0d708367
8 changed files with 355 additions and 374 deletions

View File

@@ -13,19 +13,19 @@ from httprunner.compat import OrderedDict
project_mapping = {
"debugtalk": {},
"env": {},
"tests": {
"api": {},
"testcases": {}
}
"def-api": {},
"def-testcase": {}
}
""" dict: save project loaded api/testcases, environments and debugtalk.py module.
""" dict: save project loaded api/testcases definitions, environments and debugtalk.py module.
"""
testcases_cache_mapping = {}
###############################################################################
## file loader
###############################################################################
def _check_format(file_path, content):
""" check testcase format if valid
"""
@@ -367,84 +367,12 @@ def get_module_item(module_mapping, item_type, item_name):
## testcase loader
###############################################################################
overall_def_dict = {
"api": {},
"suite": {}
}
testcases_cache_mapping = {}
def _load_test_dependencies():
""" load all api and suite definitions.
default api folder is "$CWD/tests/api/".
default suite folder is "$CWD/tests/suite/".
"""
# TODO: cache api and suite loading
# load api definitions
api_def_folder = os.path.join(os.getcwd(), "tests", "api")
for test_file in load_folder_files(api_def_folder):
_load_api_file(test_file)
# load suite definitions
suite_def_folder = os.path.join(os.getcwd(), "tests", "suite")
for suite_file in load_folder_files(suite_def_folder):
suite = _load_test_file(suite_file)
if "def" not in suite["config"]:
raise exceptions.ParamsError("def missed in suite file: {}!".format(suite_file))
call_func = suite["config"]["def"]
function_meta = parser.parse_function(call_func)
suite["function_meta"] = function_meta
overall_def_dict["suite"][function_meta["func_name"]] = suite
def _load_api_file(file_path):
""" load api definition from file and store in overall_def_dict["api"]
api file should be in format below:
[
{
"api": {
"def": "api_login",
"request": {},
"validate": []
}
},
{
"api": {
"def": "api_logout",
"request": {},
"validate": []
}
}
]
"""
api_items = load_file(file_path)
if not isinstance(api_items, list):
raise exceptions.FileFormatError("API format error: {}".format(file_path))
for api_item in api_items:
if not isinstance(api_item, dict) or len(api_item) != 1:
raise exceptions.FileFormatError("API format error: {}".format(file_path))
key, api_dict = api_item.popitem()
if key != "api" or not isinstance(api_dict, dict) or "def" not in api_dict:
raise exceptions.FileFormatError("API format error: {}".format(file_path))
api_def = api_dict.pop("def")
function_meta = parser.parse_function(api_def)
func_name = function_meta["func_name"]
if func_name in overall_def_dict["api"]:
logger.log_warning("API definition duplicated: {}".format(func_name))
api_dict["function_meta"] = function_meta
overall_def_dict["api"][func_name] = api_dict
def _load_test_file(file_path):
""" load testcase file or testsuite file
@param file_path: absolute valid file path
file_path should be in format below:
Args:
file_path (str): absolute valid file path. file_path should be in the following format:
[
{
"config": {
@@ -460,6 +388,13 @@ def _load_test_file(file_path):
"validate": []
}
},
{
"test": {
"name": "add product to cart",
"suite": "create_and_check()",
"validate": []
}
},
{
"test": {
"name": "checkout cart",
@@ -468,19 +403,24 @@ def _load_test_file(file_path):
}
}
]
@return testset dict
{
"config": {},
"testcases": [testcase11, testcase12]
}
Returns:
dict: testcase dict
{
"config": {},
"teststeps": [teststep11, teststep12]
}
"""
testset = {
testcase = {
"config": {
"path": file_path
},
"testcases": [] # TODO: rename to tests
"teststeps": []
}
for item in load_file(file_path):
# TODO: add json schema validation
if not isinstance(item, dict) or len(item) != 1:
raise exceptions.FileFormatError("Testcase format error: {}".format(file_path))
@@ -489,43 +429,69 @@ def _load_test_file(file_path):
raise exceptions.FileFormatError("Testcase format error: {}".format(file_path))
if key == "config":
testset["config"].update(test_block)
testcase["config"].update(test_block)
elif key == "test":
def extend_api_definition(block):
ref_call = block["api"]
def_block = _get_block_by_name(ref_call, "def-api")
_extend_block(block, def_block)
# reference api
if "api" in test_block:
ref_call = test_block["api"]
def_block = _get_block_by_name(ref_call, "api")
_override_block(def_block, test_block)
testset["testcases"].append(test_block)
elif "suite" in test_block:
extend_api_definition(test_block)
testcase["teststeps"].append(test_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, "suite")
testset["testcases"].extend(block["testcases"])
block = _get_block_by_name(ref_call, "def-testcase")
# TODO: bugfix lost block config variables
for teststep in block["teststeps"]:
if "api" in teststep:
extend_api_definition(teststep)
testcase["teststeps"].append(teststep)
# define directly
else:
testset["testcases"].append(test_block)
testcase["teststeps"].append(test_block)
else:
logger.log_warning(
"unexpected block key: {}. block key should only be 'config' or 'test'.".format(key)
)
return testset
return testcase
def _get_block_by_name(ref_call, ref_type):
""" get test content by reference name
@params:
ref_call: e.g. api_v1_Account_Login_POST($UserName, $Password)
ref_type: "api" or "suite"
""" get test content by reference name.
Args:
ref_call (str): call function.
e.g. api_v1_Account_Login_POST($UserName, $Password)
ref_type (enum): "def-api" or "def-testcase"
Returns:
dict: api/testcase definition.
Raises:
exceptions.ParamsError: call args number is not equal to defined args number.
"""
function_meta = parser.parse_function(ref_call)
func_name = function_meta["func_name"]
call_args = function_meta["args"]
block = _get_test_definition(func_name, ref_type)
def_args = block.get("function_meta").get("args", [])
def_args = block.get("function_meta", {}).get("args", [])
if len(call_args) != len(def_args):
raise exceptions.ParamsError("call args mismatch defined args!")
err_msg = "{}: call args number is not equal to defined args number!\n".format(func_name)
err_msg += "defined args: {}\n".format(def_args)
err_msg += "reference args: {}".format(call_args)
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
args_mapping = {}
for index, item in enumerate(def_args):
@@ -542,80 +508,104 @@ def _get_block_by_name(ref_call, ref_type):
def _get_test_definition(name, ref_type):
""" get expected api or testcase.
@params:
name: api or testcase name
ref_type: "api" or "suite"
@return
expected api info if found, otherwise raise ApiNotFound exception
Args:
name (str): api or testcase name
ref_type (enum): "def-api" or "def-testcase"
Returns:
dict: expected api/testcase info if found.
Raises:
exceptions.ApiNotFound: api not found
exceptions.TestcaseNotFound: testcase not found
"""
block = overall_def_dict.get(ref_type, {}).get(name)
block = project_mapping.get(ref_type, {}).get(name)
if not block:
err_msg = "{} not found!".format(name)
if ref_type == "api":
if ref_type == "def-api":
raise exceptions.ApiNotFound(err_msg)
else:
# ref_type == "suite":
# ref_type == "def-testcase":
raise exceptions.TestcaseNotFound(err_msg)
return block
def _override_block(def_block, current_block):
""" override def_block with current_block
@param def_block:
{
"name": "get token",
"request": {...},
"validate": [{'eq': ['status_code', 200]}]
}
@param current_block:
{
"name": "get token",
"extract": [{"token": "content.token"}],
"validate": [{'eq': ['status_code', 201]}, {'len_eq': ['content.token', 16]}]
}
@return
{
"name": "get token",
"request": {...},
"extract": [{"token": "content.token"}],
"validate": [{'eq': ['status_code', 201]}, {'len_eq': ['content.token', 16]}]
}
def _extend_block(ref_block, def_block):
""" extend ref_block with def_block.
Args:
def_block (dict): api definition dict.
ref_block (dict): reference block
Returns:
dict: extended reference block.
Examples:
>>> def_block = {
"name": "get token 1",
"request": {...},
"validate": [{'eq': ['status_code', 200]}]
}
>>> ref_block = {
"name": "get token 2",
"extract": [{"token": "content.token"}],
"validate": [{'eq': ['status_code', 201]}, {'len_eq': ['content.token', 16]}]
}
>>> _extend_block(def_block, ref_block)
{
"name": "get token 2",
"request": {...},
"extract": [{"token": "content.token"}],
"validate": [{'eq': ['status_code', 201]}, {'len_eq': ['content.token', 16]}]
}
"""
# TODO: override variables
def_validators = def_block.get("validate") or def_block.get("validators", [])
current_validators = current_block.get("validate") or current_block.get("validators", [])
ref_validators = ref_block.get("validate") or ref_block.get("validators", [])
def_extrators = def_block.get("extract") \
or def_block.get("extractors") \
or def_block.get("extract_binds", [])
current_extractors = current_block.get("extract") \
or current_block.get("extractors") \
or current_block.get("extract_binds", [])
ref_extractors = ref_block.get("extract") \
or ref_block.get("extractors") \
or ref_block.get("extract_binds", [])
current_block.update(def_block)
current_block["validate"] = _merge_validator(
ref_block.update(def_block)
ref_block["validate"] = _merge_validator(
def_validators,
current_validators
ref_validators
)
current_block["extract"] = _merge_extractor(
ref_block["extract"] = _merge_extractor(
def_extrators,
current_extractors
ref_extractors
)
def _get_validators_mapping(validators):
""" get validators mapping from api or test validators
@param (list) validators:
[
{"check": "v1", "expect": 201, "comparator": "eq"},
{"check": {"b": 1}, "expect": 200, "comparator": "eq"}
]
@return
{
("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"},
('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"}
}
def _convert_validators_to_mapping(validators):
""" convert validators list to mapping.
Args:
validators (list): validators in list
Returns:
dict: validators mapping, use (check, comparator) as key.
Examples:
>>> validators = [
{"check": "v1", "expect": 201, "comparator": "eq"},
{"check": {"b": 1}, "expect": 200, "comparator": "eq"}
]
>>> _convert_validators_to_mapping(validators)
{
("v1", "eq"): {"check": "v1", "expect": 201, "comparator": "eq"},
('{"b": 1}', "eq"): {"check": {"b": 1}, "expect": 200, "comparator": "eq"}
}
"""
validators_mapping = {}
@@ -633,48 +623,66 @@ def _get_validators_mapping(validators):
return validators_mapping
def _merge_validator(def_validators, current_validators):
""" merge def_validators with current_validators
@params:
def_validators: [{'eq': ['v1', 200]}, {"check": "s2", "expect": 16, "comparator": "len_eq"}]
current_validators: [{"check": "v1", "expect": 201}, {'len_eq': ['s3', 12]}]
@return:
[
{"check": "v1", "expect": 201, "comparator": "eq"},
{"check": "s2", "expect": 16, "comparator": "len_eq"},
{"check": "s3", "expect": 12, "comparator": "len_eq"}
]
def _merge_validator(def_validators, ref_validators):
""" merge def_validators with ref_validators.
Args:
def_validators (list):
ref_validators (list):
Returns:
list: merged 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)
[
{"check": "v1", "expect": 201, "comparator": "eq"},
{"check": "s2", "expect": 16, "comparator": "len_eq"},
{"check": "s3", "expect": 12, "comparator": "len_eq"}
]
"""
if not def_validators:
return current_validators
return ref_validators
elif not current_validators:
elif not ref_validators:
return def_validators
else:
api_validators_mapping = _get_validators_mapping(def_validators)
test_validators_mapping = _get_validators_mapping(current_validators)
def_validators_mapping = _convert_validators_to_mapping(def_validators)
ref_validators_mapping = _convert_validators_to_mapping(ref_validators)
api_validators_mapping.update(test_validators_mapping)
return list(api_validators_mapping.values())
def_validators_mapping.update(ref_validators_mapping)
return list(def_validators_mapping.values())
def _merge_extractor(def_extrators, current_extractors):
""" merge def_extrators with current_extractors
@params:
def_extrators: [{"var1": "val1"}, {"var2": "val2"}]
current_extractors: [{"var1": "val111"}, {"var3": "val3"}]
@return:
[
{"var1": "val111"},
{"var2": "val2"},
{"var3": "val3"}
]
def _merge_extractor(def_extrators, ref_extractors):
""" merge def_extrators with ref_extractors
Args:
def_extrators (list): [{"var1": "val1"}, {"var2": "val2"}]
ref_extractors (list): [{"var1": "val111"}, {"var3": "val3"}]
Returns:
list: merged extractors
Examples:
>>> def_extrators = [{"var1": "val1"}, {"var2": "val2"}]
>>> ref_extractors = [{"var1": "val111"}, {"var3": "val3"}]
>>> _merge_extractor(def_extrators, ref_extractors)
[
{"var1": "val111"},
{"var2": "val2"},
{"var3": "val3"}
]
"""
if not def_extrators:
return current_extractors
return ref_extractors
elif not current_extractors:
elif not ref_extractors:
return def_extrators
else:
@@ -687,7 +695,7 @@ def _merge_extractor(def_extrators, current_extractors):
var_name = list(api_extrator.keys())[0]
extractor_dict[var_name] = api_extrator[var_name]
for test_extrator in current_extractors:
for test_extrator in ref_extractors:
if len(test_extrator) != 1:
logger.log_warning("incorrect extractor: {}".format(test_extrator))
continue
@@ -702,63 +710,6 @@ def _merge_extractor(def_extrators, current_extractors):
return extractor_list
def load_testcases(path):
""" load testcases from file path
Args:
path (str): testcase file/foler path.
path could be in several types:
- absolute/relative file path
- absolute/relative folder path
- list/set container with file(s) and/or folder(s)
Returns:
list: testcases list, each testcase is corresponding to a file
[
testcase_dict_1,
testcase_dict_2
]
"""
if isinstance(path, (list, set)):
testcases_list = []
for file_path in set(path):
testcases = load_testcases(file_path)
if not testcases:
continue
testcases_list.extend(testcases)
return testcases_list
if not os.path.isabs(path):
path = os.path.join(os.getcwd(), path)
if path in testcases_cache_mapping:
return testcases_cache_mapping[path]
if os.path.isdir(path):
files_list = load_folder_files(path)
testcases_list = load_testcases(files_list)
elif os.path.isfile(path):
try:
testcase = _load_test_file(path)
if testcase["testcases"]:
testcases_list = [testcase]
else:
testcases_list = []
except exceptions.FileFormatError:
testcases_list = []
else:
err_msg = "path not exist: {}".format(path)
logger.log_error(err_msg)
raise exceptions.FileNotFound(err_msg)
testcases_cache_mapping[path] = testcases_list
return testcases_list
def load_folder_content(folder_path):
""" load api/testcases/testsuites definitions from folder.
@@ -843,7 +794,7 @@ def load_api_folder(api_folder_path=None):
api_dict["function_meta"] = function_meta
api_definition_mapping[func_name] = api_dict
project_mapping["tests"]["api"] = api_definition_mapping
project_mapping["def-api"] = api_definition_mapping
return api_definition_mapping
@@ -874,7 +825,7 @@ def load_test_folder(test_folder_path=None):
dict: testcases definition mapping.
{
"tests/testcases/setup.yml": [
"create_and_check": [
{"config": {}},
{"test": {}},
{"test": {}}
@@ -900,7 +851,7 @@ def load_test_folder(test_folder_path=None):
"config": {
"path": test_file_path
},
"tests": []
"teststeps": []
}
for item in items:
key, block = item.popitem()
@@ -919,13 +870,13 @@ def load_test_folder(test_folder_path=None):
if func_name in test_definition_mapping:
logger.log_warning("API definition duplicated: {}".format(func_name))
block["function_meta"] = function_meta
testcase["function_meta"] = function_meta
test_definition_mapping[func_name] = testcase
else:
# key == "test":
testcase["tests"].append(block)
testcase["teststeps"].append(block)
project_mapping["tests"]["testcases"] = test_definition_mapping
project_mapping["def-testcase"] = test_definition_mapping
return test_definition_mapping
@@ -949,15 +900,59 @@ def load_project_tests(folder_path=None):
return project_mapping
def load(path):
""" main interface for loading testcases
def load_testcases(path):
""" load testcases from file path
Args:
path (str): testcase file/folder path
path (str): testcase file/foler path.
path could be in several types:
- absolute/relative file path
- absolute/relative folder path
- list/set container with file(s) and/or folder(s)
Returns:
list: testcases list
list: testcases list, each testcase is corresponding to a file
[
testcase_dict_1,
testcase_dict_2
]
"""
_load_test_dependencies()
return load_testcases(path)
if isinstance(path, (list, set)):
testcases_list = []
for file_path in set(path):
testcases = load_testcases(file_path)
if not testcases:
continue
testcases_list.extend(testcases)
return testcases_list
if not os.path.isabs(path):
path = os.path.join(os.getcwd(), path)
if path in testcases_cache_mapping:
return testcases_cache_mapping[path]
if os.path.isdir(path):
files_list = load_folder_files(path)
testcases_list = load_testcases(files_list)
elif os.path.isfile(path):
try:
testcase = _load_test_file(path)
if testcase["teststeps"]:
testcases_list = [testcase]
else:
testcases_list = []
except exceptions.FileFormatError:
testcases_list = []
else:
err_msg = "path not exist: {}".format(path)
logger.log_error(err_msg)
raise exceptions.FileNotFound(err_msg)
testcases_cache_mapping[path] = testcases_list
return testcases_list

View File

@@ -40,7 +40,7 @@ def gen_locustfile(testcase_file_path):
"templates",
"locustfile_template"
)
testcases = loader.load(testcase_file_path)
testcases = loader.load_testcases(testcase_file_path)
host = testcases[0].get("config", {}).get("request", {}).get("base_url", "")
with io.open(template_path, encoding='utf-8') as template:

View File

@@ -34,39 +34,39 @@ class TestCase(unittest.TestCase):
class TestSuite(unittest.TestSuite):
""" create test suite with a testset, it may include one or several testcases.
each suite should initialize a separate Runner() with testset config.
@param
(dict) testset
""" create test suite with a testcase, it may include one or several teststeps.
each suite should initialize a separate Runner() with testcase config.
Args:
testcase (dict): testcase dict
{
"name": "testset description",
"config": {
"name": "testset description",
"name": "testcase description",
"parameters": {},
"variables": [],
"request": {},
"output": []
},
"testcases": [
"teststeps": [
{
"name": "testcase description",
"name": "teststep1 description",
"parameters": {},
"variables": [], # optional, override
"request": {},
"extract": {}, # optional
"validate": {} # optional
},
testcase12
teststep2
]
}
(dict) variables_mapping:
passed in variables mapping, it will override variables in config block
variables_mapping (dict): passed in variables mapping, it will override variables in config block.
"""
def __init__(self, testset, variables_mapping=None, http_client_session=None):
def __init__(self, testcase, variables_mapping=None, http_client_session=None):
super(TestSuite, self).__init__()
self.test_runner_list = []
self.config = testset.get("config", {})
self.config = testcase.get("config", {})
self.output_variables_list = self.config.get("output", [])
self.testset_file_path = self.config.get("path")
config_dict_parameters = self.config.get("parameters", [])
@@ -80,22 +80,22 @@ class TestSuite(unittest.TestSuite):
config_dict_parameters
)
self.testcase_parser = context.TestcaseParser()
testcases = testset.get("testcases", [])
teststeps = testcase.get("teststeps", [])
for config_variables in config_parametered_variables_list:
# config level
self.config["variables"] = config_variables
test_runner = runner.Runner(self.config, http_client_session)
for testcase_dict in testcases:
testcase_dict = copy.copy(testcase_dict)
for teststep_dict in teststeps:
teststep_dict = copy.copy(teststep_dict)
# testcase level
testcase_parametered_variables_list = self._get_parametered_variables(
testcase_dict.get("variables", []),
testcase_dict.get("parameters", [])
teststep_dict.get("variables", []),
teststep_dict.get("parameters", [])
)
for testcase_variables in testcase_parametered_variables_list:
testcase_dict["variables"] = testcase_variables
teststep_dict["variables"] = testcase_variables
# eval testcase name with bind variables
variables = utils.override_variables_binds(
@@ -104,13 +104,13 @@ class TestSuite(unittest.TestSuite):
)
self.testcase_parser.update_binded_variables(variables)
try:
testcase_name = self.testcase_parser.eval_content_with_bindings(testcase_dict["name"])
testcase_name = self.testcase_parser.eval_content_with_bindings(teststep_dict["name"])
except (AssertionError, exceptions.ParamsError):
logger.log_warning("failed to eval testcase name: {}".format(testcase_dict["name"]))
testcase_name = testcase_dict["name"]
logger.log_warning("failed to eval testcase name: {}".format(teststep_dict["name"]))
testcase_name = teststep_dict["name"]
self.test_runner_list.append((test_runner, variables))
self._add_test_to_suite(testcase_name, test_runner, testcase_dict)
self._add_test_to_suite(testcase_name, test_runner, teststep_dict)
def _get_parametered_variables(self, variables, parameters):
""" parameterize varaibles with parameters
@@ -166,15 +166,14 @@ def init_test_suites(path_or_testcases, mapping=None, http_client_session=None):
Args:
path_or_testcases (str/dict/list): testcase file path or testcase dict or testcases list
testset_dict
testcase_dict
or
[
testset_dict_1,
testset_dict_2,
testcase_dict_1,
testcase_dict_2,
{
"config": {},
"api": {},
"testcases": [testcase11, testcase12]
"teststeps": [teststep11, teststep12]
}
]
@@ -188,7 +187,7 @@ def init_test_suites(path_or_testcases, mapping=None, http_client_session=None):
if validator.is_testcases(path_or_testcases):
testcases = path_or_testcases
else:
testcases = loader.load(path_or_testcases)
testcases = loader.load_testcases(path_or_testcases)
# TODO: move comparator uniform here
mapping = mapping or {}
@@ -237,12 +236,12 @@ class HttpRunner(object):
""" start to run test with varaibles mapping.
Args:
path_or_testcases (str/list/dict): YAML/JSON testset file path or testset list
path_or_testcases (str/list/dict): YAML/JSON testcase file path or testcase list
path: path could be in several type
- absolute/relative file path
- absolute/relative folder path
- list/set container with file(s) and/or folder(s)
testsets: testset or list of testset
testcases: testcase dict or list of testcases
- (dict) testset_dict
- (list) list of testset_dict
[

View File

@@ -6,35 +6,48 @@ TODO: refactor with JSON schema validate
"""
def is_testcase(data_structure):
""" check if data_structure is a testcase
testcase should always be in the following data structure:
{
"name": "desc1",
"config": {},
"api": {},
"testcases": [testcase11, testcase12]
}
""" check if data_structure is a testcase.
Args:
data_structure (dict): testcase should always be in the following data structure:
{
"name": "desc1",
"config": {},
"api": {},
"testcases": [testcase11, testcase12]
}
Returns:
bool: True if data_structure is valid testcase, otherwise False.
"""
if not isinstance(data_structure, dict):
return False
if "name" not in data_structure or "testcases" not in data_structure:
if "name" not in data_structure or "teststeps" not in data_structure:
return False
if not isinstance(data_structure["testcases"], list):
if not isinstance(data_structure["teststeps"], list):
return False
return True
def is_testcases(data_structure):
""" check if data_structure is testcase or testcases list
testsets should always be in the following data structure:
testset_dict
or
[
testset_dict_1,
testset_dict_2
]
""" check if data_structure is testcase or testcases list.
Args:
data_structure (dict): testcase(s) should always be in the following data structure:
testcase_dict
or
[
testcase_dict_1,
testcase_dict_2
]
Returns:
bool: True if data_structure is valid testcase(s), otherwise False.
"""
if not isinstance(data_structure, list):
return is_testcase(data_structure)

View File

@@ -23,7 +23,7 @@ class TestHttpRunner(ApiServerUnittest):
'output': ['token']
},
'api': {},
'testcases': [
'teststeps': [
{
'name': '/api/get-token',
'request': {
@@ -114,7 +114,7 @@ class TestHttpRunner(ApiServerUnittest):
testsets = [
{
"name": "post data",
"testcases": [
"teststeps": [
{
"name": "post data",
"request": {

View File

@@ -219,60 +219,34 @@ class TestModuleLoader(unittest.TestCase):
class TestSuiteLoader(unittest.TestCase):
def setUp(self):
loader.overall_def_dict = {
"api": {},
"suite": {}
}
def test_load_test_dependencies(self):
loader._load_test_dependencies()
overall_def_dict = loader.overall_def_dict
self.assertIn("get_token", overall_def_dict["api"])
self.assertIn("create_and_check", overall_def_dict["suite"])
def test_load_api_file(self):
loader._load_api_file("tests/api/basic.yml")
overall_api_def_dict = loader.overall_def_dict["api"]
self.assertIn("get_token",overall_api_def_dict)
self.assertEqual("/api/get-token", overall_api_def_dict["get_token"]["request"]["url"])
self.assertIn("$user_agent", overall_api_def_dict["get_token"]["function_meta"]["args"])
self.assertEqual(len(overall_api_def_dict["get_token"]["validate"]), 3)
def test_load_test_file_suite(self):
loader._load_api_file("tests/api/basic.yml")
testset = loader._load_test_file("tests/suite/create_and_get.yml")
self.assertEqual(testset["config"]["name"], "create user and check result.")
self.assertEqual(len(testset["testcases"]), 3)
self.assertEqual(testset["testcases"][0]["name"], "make sure user $uid does not exist")
self.assertEqual(testset["testcases"][0]["request"]["url"], "/api/users/$uid")
@classmethod
def setUpClass(cls):
project_dir = os.path.join(os.getcwd(), "tests")
loader.load_project_tests(project_dir)
def test_load_test_file_testcase(self):
loader._load_test_dependencies()
testset = loader._load_test_file("tests/testcases/smoketest.yml")
self.assertEqual(testset["config"]["name"], "smoketest")
self.assertEqual(testset["config"]["path"], "tests/testcases/smoketest.yml")
self.assertIn("device_sn", testset["config"]["variables"][0])
self.assertEqual(len(testset["testcases"]), 8)
self.assertEqual(testset["testcases"][0]["name"], "get token")
testcase = loader._load_test_file("tests/testcases/smoketest.yml")
self.assertEqual(testcase["config"]["name"], "smoketest")
self.assertEqual(testcase["config"]["path"], "tests/testcases/smoketest.yml")
self.assertIn("device_sn", testcase["config"]["variables"][0])
self.assertEqual(len(testcase["teststeps"]), 8)
self.assertEqual(testcase["teststeps"][0]["name"], "get token")
def test_get_block_by_name(self):
loader._load_test_dependencies()
ref_call = "get_user($uid, $token)"
block = loader._get_block_by_name(ref_call, "api")
block = loader._get_block_by_name(ref_call, "def-api")
self.assertEqual(block["request"]["url"], "/api/users/$uid")
self.assertEqual(block["function_meta"]["func_name"], "get_user")
self.assertEqual(block["function_meta"]["args"], ['$uid', '$token'])
def test_get_block_by_name_args_mismatch(self):
loader._load_test_dependencies()
ref_call = "get_user($uid, $token, $var)"
with self.assertRaises(exceptions.ParamsError):
loader._get_block_by_name(ref_call, "api")
loader._get_block_by_name(ref_call, "def-api")
def test_override_block(self):
loader._load_test_dependencies()
def_block = loader._get_block_by_name("get_token($user_agent, $device_sn, $os_platform, $app_version)", "api")
def_block = loader._get_block_by_name(
"get_token($user_agent, $device_sn, $os_platform, $app_version)", "def-api")
test_block = {
"name": "override block",
"variables": [
@@ -286,28 +260,26 @@ class TestSuiteLoader(unittest.TestCase):
]
}
loader._override_block(def_block, test_block)
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"])
def test_get_test_definition_api(self):
loader._load_test_dependencies()
api_def = loader._get_test_definition("get_headers", "api")
api_def = loader._get_test_definition("get_headers", "def-api")
self.assertEqual(api_def["request"]["url"], "/headers")
self.assertEqual(len(api_def["setup_hooks"]), 2)
self.assertEqual(len(api_def["teardown_hooks"]), 1)
with self.assertRaises(exceptions.ApiNotFound):
loader._get_test_definition("get_token_XXX", "api")
loader._get_test_definition("get_token_XXX", "def-api")
def test_get_test_definition_suite(self):
loader._load_test_dependencies()
api_def = loader._get_test_definition("create_and_check", "suite")
api_def = loader._get_test_definition("create_and_check", "def-testcase")
self.assertEqual(api_def["config"]["name"], "create user and check result.")
with self.assertRaises(exceptions.TestcaseNotFound):
loader._get_test_definition("create_and_check_XXX", "suite")
loader._get_test_definition("create_and_check_XXX", "def-testcase")
def test_merge_validator(self):
def_validators = [
@@ -376,7 +348,7 @@ class TestSuiteLoader(unittest.TestCase):
self.assertEqual(len(testset_list), 1)
self.assertIn("path", testset_list[0]["config"])
self.assertEqual(testset_list[0]["config"]["path"], path)
self.assertEqual(len(testset_list[0]["testcases"]), 3)
self.assertEqual(len(testset_list[0]["teststeps"]), 3)
testsets_list.extend(testset_list)
# relative file path
@@ -385,7 +357,7 @@ class TestSuiteLoader(unittest.TestCase):
self.assertEqual(len(testset_list), 1)
self.assertIn("path", testset_list[0]["config"])
self.assertIn(path, testset_list[0]["config"]["path"])
self.assertEqual(len(testset_list[0]["testcases"]), 3)
self.assertEqual(len(testset_list[0]["teststeps"]), 3)
testsets_list.extend(testset_list)
# list/set container with file(s)
@@ -395,20 +367,19 @@ class TestSuiteLoader(unittest.TestCase):
]
testset_list = loader.load_testcases(path)
self.assertEqual(len(testset_list), 2)
self.assertEqual(len(testset_list[0]["testcases"]), 3)
self.assertEqual(len(testset_list[1]["testcases"]), 3)
self.assertEqual(len(testset_list[0]["teststeps"]), 3)
self.assertEqual(len(testset_list[1]["teststeps"]), 3)
testsets_list.extend(testset_list)
self.assertEqual(len(testsets_list), 4)
for testset in testsets_list:
for test in testset["testcases"]:
for test in testset["teststeps"]:
self.assertIn('name', test)
self.assertIn('request', test)
self.assertIn('url', test['request'])
self.assertIn('method', test['request'])
def test_load_testcases_by_path_folder(self):
loader._load_test_dependencies()
# absolute folder path
path = os.path.join(os.getcwd(), 'tests/data')
testset_list_1 = loader.load_testcases(path)
@@ -447,15 +418,14 @@ class TestSuiteLoader(unittest.TestCase):
loader.load_testcases(path)
def test_load_testcases_by_path_layered(self):
loader._load_test_dependencies()
path = os.path.join(
os.getcwd(), 'tests/data/demo_testset_layer.yml')
testsets_list = loader.load_testcases(path)
self.assertIn("variables", testsets_list[0]["config"])
self.assertIn("request", testsets_list[0]["config"])
self.assertIn("request", testsets_list[0]["testcases"][0])
self.assertIn("url", testsets_list[0]["testcases"][0]["request"])
self.assertIn("validate", testsets_list[0]["testcases"][0])
self.assertIn("request", testsets_list[0]["teststeps"][0])
self.assertIn("url", testsets_list[0]["teststeps"][0]["request"])
self.assertIn("validate", testsets_list[0]["teststeps"][0])
def test_load_folder_content(self):
path = os.path.join(os.getcwd(), "tests", "api")
@@ -481,6 +451,10 @@ class TestSuiteLoader(unittest.TestCase):
testcases_definition_mapping["setup_and_reset"]["config"]["name"],
"setup and reset all."
)
self.assertEqual(
testcases_definition_mapping["setup_and_reset"]["function_meta"]["func_name"],
"setup_and_reset"
)
def test_load_testsuites_folder(self):
path = os.path.join(os.getcwd(), "tests", "testcases")
@@ -500,18 +474,18 @@ class TestSuiteLoader(unittest.TestCase):
project_dir = os.path.join(os.getcwd(), "tests")
project_tests = loader.load_project_tests(project_dir)
self.assertEqual(project_tests["debugtalk"]["variables"]["SECRET_KEY"], "DebugTalk")
self.assertIn("get_token", project_tests["tests"]["api"])
self.assertIn("setup_and_reset", project_tests["tests"]["testcases"])
self.assertIn("get_token", project_tests["def-api"])
self.assertIn("setup_and_reset", project_tests["def-testcase"])
def test_loader(self):
hrunner = task.HttpRunner(dot_env_path="tests/data/test.env")
self.assertEqual(hrunner.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH")
self.assertIn("debugtalk", hrunner.project_mapping)
self.assertIn("setup_and_reset", hrunner.project_mapping["tests"]["testcases"])
self.assertIn("setup_and_reset", hrunner.project_mapping["def-testcase"])
self.assertEqual(
hrunner.project_mapping["debugtalk"]["variables"]["SECRET_KEY"],
"DebugTalk"
)
self.assertIn("get_sign", hrunner.project_mapping["debugtalk"]["functions"])
self.assertIn("get_token", hrunner.project_mapping["tests"]["api"])
self.assertIn("setup_and_reset", hrunner.project_mapping["tests"]["testcases"])
self.assertIn("get_token", hrunner.project_mapping["def-api"])
self.assertIn("setup_and_reset", hrunner.project_mapping["def-testcase"])

View File

@@ -170,7 +170,7 @@ class TestRunner(ApiServerUnittest):
"config": {
'path': 'tests/httpbin/hooks.yml',
},
"testcases": [
"teststeps": [
{
"name": "test teardown hooks",
"request": {
@@ -206,7 +206,7 @@ class TestRunner(ApiServerUnittest):
"config": {
'path': 'tests/httpbin/hooks.yml',
},
"testcases": [
"teststeps": [
{
"name": "test teardown hooks",
"request": {
@@ -236,7 +236,7 @@ class TestRunner(ApiServerUnittest):
"config": {
'path': 'tests/httpbin/hooks.yml',
},
"testcases": [
"teststeps": [
{
"name": "test teardown hooks",
"request": {
@@ -384,7 +384,7 @@ class TestRunner(ApiServerUnittest):
testsets = loader.load_testcases(testcase_file_path)
testset = testsets[0]
config_dict_headers = testset["config"]["request"]["headers"]
test_dict_headers = testset["testcases"][0]["request"]["headers"]
test_dict_headers = testset["teststeps"][0]["request"]["headers"]
headers = deep_update_dict(
config_dict_headers,
test_dict_headers

View File

@@ -37,7 +37,7 @@ class TestTask(ApiServerUnittest):
'output': ['token']
},
'api': {},
'testcases': [
'teststeps': [
{
'name': '/api/get-token',
'request': {