mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-09 01:39:39 +08:00
call HttpRunner class with testsets data structure other than path
This commit is contained in:
@@ -152,30 +152,34 @@ class TaskSuite(unittest.TestSuite):
|
|||||||
""" create task suite with specified testcase path.
|
""" create task suite with specified testcase path.
|
||||||
each task suite may include one or several test suite.
|
each task suite may include one or several test suite.
|
||||||
"""
|
"""
|
||||||
def __init__(self, path, mapping=None, http_client_session=None):
|
def __init__(self, testsets, mapping=None, http_client_session=None):
|
||||||
"""
|
"""
|
||||||
@params
|
@params
|
||||||
path: path could be in several type
|
testsets (dict/list): testset or list of testset
|
||||||
- absolute/relative file path
|
testset_dict
|
||||||
- absolute/relative folder path
|
or
|
||||||
- list/set container with file(s) and/or folder(s)
|
[
|
||||||
(dict) mapping:
|
testset_dict_1,
|
||||||
|
testset_dict_2,
|
||||||
|
{
|
||||||
|
"name": "desc1",
|
||||||
|
"config": {},
|
||||||
|
"api": {},
|
||||||
|
"testcases": [testcase11, testcase12]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mapping (dict):
|
||||||
passed in variables mapping, it will override variables in config block
|
passed in variables mapping, it will override variables in config block
|
||||||
"""
|
"""
|
||||||
super(TaskSuite, self).__init__()
|
super(TaskSuite, self).__init__()
|
||||||
mapping = mapping or {}
|
mapping = mapping or {}
|
||||||
|
|
||||||
if not isinstance(path, list):
|
|
||||||
# absolute/relative file/folder path
|
|
||||||
path = [path]
|
|
||||||
|
|
||||||
# remove duplicate path
|
|
||||||
path = set(path)
|
|
||||||
|
|
||||||
testsets = testcase.load_testcases_by_path(path)
|
|
||||||
if not testsets:
|
if not testsets:
|
||||||
raise exception.TestcaseNotFound
|
raise exception.TestcaseNotFound
|
||||||
|
|
||||||
|
if isinstance(testsets, dict):
|
||||||
|
testsets = [testsets]
|
||||||
|
|
||||||
self.suite_list = []
|
self.suite_list = []
|
||||||
for testset in testsets:
|
for testset in testsets:
|
||||||
suite = TestSuite(testset, mapping, http_client_session)
|
suite = TestSuite(testset, mapping, http_client_session)
|
||||||
@@ -187,6 +191,18 @@ class TaskSuite(unittest.TestSuite):
|
|||||||
return self.suite_list
|
return self.suite_list
|
||||||
|
|
||||||
|
|
||||||
|
def init_task_suite(path_or_testsets, mapping=None, http_client_session=None):
|
||||||
|
""" initialize task suite
|
||||||
|
"""
|
||||||
|
if not testcase.is_testsets(path_or_testsets):
|
||||||
|
testsets = testcase.load_testcases_by_path(path_or_testsets)
|
||||||
|
else:
|
||||||
|
testsets = path_or_testsets
|
||||||
|
|
||||||
|
mapping = mapping or {}
|
||||||
|
return TaskSuite(testsets, mapping, http_client_session)
|
||||||
|
|
||||||
|
|
||||||
class HttpRunner(object):
|
class HttpRunner(object):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -198,16 +214,25 @@ class HttpRunner(object):
|
|||||||
kwargs.setdefault("resultclass", HtmlTestResult)
|
kwargs.setdefault("resultclass", HtmlTestResult)
|
||||||
self.runner = unittest.TextTestRunner(**kwargs)
|
self.runner = unittest.TextTestRunner(**kwargs)
|
||||||
|
|
||||||
def run(self, path, mapping=None):
|
def run(self, path_or_testsets, mapping=None):
|
||||||
""" start to run specified testset file with varaibles mapping
|
""" start to run test with varaibles mapping
|
||||||
@param (str) path:
|
@param path_or_testsets: YAML/JSON testset file path or testset list
|
||||||
YAML/JSON testset file path
|
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
|
||||||
|
- (dict) testset_dict
|
||||||
|
- (list) list of testset_dict
|
||||||
|
[
|
||||||
|
testset_dict_1,
|
||||||
|
testset_dict_2
|
||||||
|
]
|
||||||
@param (dict) mapping:
|
@param (dict) mapping:
|
||||||
if mapping specified, it will override variables in config block
|
if mapping specified, it will override variables in config block
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
mapping = mapping or {}
|
task_suite = init_task_suite(path_or_testsets, mapping)
|
||||||
task_suite = TaskSuite(path, mapping)
|
|
||||||
except exception.TestcaseNotFound:
|
except exception.TestcaseNotFound:
|
||||||
logger.log_error("Testcases not found in {}".format(path))
|
logger.log_error("Testcases not found in {}".format(path))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -238,9 +263,8 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
class LocustTask(object):
|
class LocustTask(object):
|
||||||
|
|
||||||
def __init__(self, path, locust_client, mapping=None):
|
def __init__(self, path_or_testsets, locust_client, mapping=None):
|
||||||
mapping = mapping or {}
|
self.task_suite = init_task_suite(path_or_testsets, mapping, locust_client)
|
||||||
self.task_suite = TaskSuite(path, mapping, locust_client)
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
for suite in self.task_suite:
|
for suite in self.task_suite:
|
||||||
|
|||||||
@@ -417,6 +417,46 @@ def extend_test_api(test_block_dict):
|
|||||||
test_extracors
|
test_extracors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_testset(data_structure):
|
||||||
|
""" check if data_structure is a testset
|
||||||
|
testset should always be in the following data structure:
|
||||||
|
{
|
||||||
|
"name": "desc1",
|
||||||
|
"config": {},
|
||||||
|
"api": {},
|
||||||
|
"testcases": [testcase11, testcase12]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if not isinstance(data_structure, dict):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if "name" not in data_structure or "testcases" not in data_structure:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(data_structure["testcases"], list):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_testsets(data_structure):
|
||||||
|
""" check if data_structure is testset or testsets
|
||||||
|
testsets should always be in the following data structure:
|
||||||
|
testset_dict
|
||||||
|
or
|
||||||
|
[
|
||||||
|
testset_dict_1,
|
||||||
|
testset_dict_2
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
if not isinstance(data_structure, list):
|
||||||
|
return is_testset(data_structure)
|
||||||
|
|
||||||
|
for item in data_structure:
|
||||||
|
if not is_testset(item):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def load_test_file(file_path):
|
def load_test_file(file_path):
|
||||||
""" load testset file, get testset data structure.
|
""" load testset file, get testset data structure.
|
||||||
@param file_path: absolute valid testset file path
|
@param file_path: absolute valid testset file path
|
||||||
@@ -467,7 +507,9 @@ def load_test_file(file_path):
|
|||||||
testset["api"][func_name] = api_info
|
testset["api"][func_name] = api_info
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.log_warning("unexpected block: {}. block should only be config or test.".format(key))
|
logger.log_warning(
|
||||||
|
"unexpected block: {}. block should only be 'config', 'test' or 'api'.".format(key)
|
||||||
|
)
|
||||||
|
|
||||||
return testset
|
return testset
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,53 @@ class TestHttpRunner(ApiServerUnittest):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.testset_path = "tests/data/demo_testset_cli.yml"
|
self.testset_path = "tests/data/demo_testset_cli.yml"
|
||||||
|
self.testset = {
|
||||||
|
'name': 'testset description',
|
||||||
|
'config': {
|
||||||
|
'path': 'docs/data/demo-quickstart-2.yml',
|
||||||
|
'name': 'testset description',
|
||||||
|
'request': {
|
||||||
|
'base_url': '',
|
||||||
|
'headers': {'User-Agent': 'python-requests/2.18.4'}
|
||||||
|
},
|
||||||
|
'variables': [],
|
||||||
|
'output': ['token']
|
||||||
|
},
|
||||||
|
'api': {},
|
||||||
|
'testcases': [
|
||||||
|
{
|
||||||
|
'name': '/api/get-token',
|
||||||
|
'request': {
|
||||||
|
'url': 'http://127.0.0.1:5000/api/get-token',
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {'Content-Type': 'application/json', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios', 'user_agent': 'iOS/10.3'},
|
||||||
|
'json': {'sign': '958a05393efef0ac7c0fb80a7eac45e24fd40c27'}
|
||||||
|
},
|
||||||
|
'extract': [
|
||||||
|
{'token': 'content.token'}
|
||||||
|
],
|
||||||
|
'validate': [
|
||||||
|
{'eq': ['status_code', 200]},
|
||||||
|
{'eq': ['headers.Content-Type', 'application/json']},
|
||||||
|
{'eq': ['content.success', True]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': '/api/users/1000',
|
||||||
|
'request': {
|
||||||
|
'url': 'http://127.0.0.1:5000/api/users/1000',
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {'Content-Type': 'application/json', 'device_sn': 'FwgRiO7CNA50DSU','token': '$token'}, 'json': {'name': 'user1', 'password': '123456'}
|
||||||
|
},
|
||||||
|
'validate': [
|
||||||
|
{'eq': ['status_code', 201]},
|
||||||
|
{'eq': ['headers.Content-Type', 'application/json']},
|
||||||
|
{'eq': ['content.success', True]},
|
||||||
|
{'eq': ['content.msg', 'user created successfully.']}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
self.reset_all()
|
self.reset_all()
|
||||||
|
|
||||||
def reset_all(self):
|
def reset_all(self):
|
||||||
@@ -35,3 +82,19 @@ class TestHttpRunner(ApiServerUnittest):
|
|||||||
runner.gen_html_report(html_report_name=output_folder_name)
|
runner.gen_html_report(html_report_name=output_folder_name)
|
||||||
report_save_dir = os.path.join(os.getcwd(), 'reports', output_folder_name)
|
report_save_dir = os.path.join(os.getcwd(), 'reports', output_folder_name)
|
||||||
shutil.rmtree(report_save_dir)
|
shutil.rmtree(report_save_dir)
|
||||||
|
|
||||||
|
def test_run_testsets(self):
|
||||||
|
testsets = [self.testset]
|
||||||
|
runner = HttpRunner().run(testsets)
|
||||||
|
summary = runner.summary
|
||||||
|
self.assertTrue(summary["success"])
|
||||||
|
self.assertEqual(summary["stat"]["testsRun"], 2)
|
||||||
|
self.assertIn("records", summary)
|
||||||
|
|
||||||
|
def test_run_testset(self):
|
||||||
|
testsets = self.testset
|
||||||
|
runner = HttpRunner().run(testsets)
|
||||||
|
summary = runner.summary
|
||||||
|
self.assertTrue(summary["success"])
|
||||||
|
self.assertEqual(summary["stat"]["testsRun"], 2)
|
||||||
|
self.assertIn("records", summary)
|
||||||
|
|||||||
@@ -24,9 +24,57 @@ class TestTask(ApiServerUnittest):
|
|||||||
self.assertIsInstance(testcase, task.TestCase)
|
self.assertIsInstance(testcase, task.TestCase)
|
||||||
|
|
||||||
def test_create_task(self):
|
def test_create_task(self):
|
||||||
testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo_testset_variables.yml')
|
testsets = [
|
||||||
task_suite = task.TaskSuite(testcase_file_path)
|
{
|
||||||
self.assertEqual(task_suite.countTestCases(), 3)
|
'name': 'testset description',
|
||||||
|
'config': {
|
||||||
|
'path': 'docs/data/demo-quickstart-2.yml',
|
||||||
|
'name': 'testset description',
|
||||||
|
'request': {
|
||||||
|
'base_url': '',
|
||||||
|
'headers': {'User-Agent': 'python-requests/2.18.4'}
|
||||||
|
},
|
||||||
|
'variables': [],
|
||||||
|
'output': ['token']
|
||||||
|
},
|
||||||
|
'api': {},
|
||||||
|
'testcases': [
|
||||||
|
{
|
||||||
|
'name': '/api/get-token',
|
||||||
|
'request': {
|
||||||
|
'url': 'http://127.0.0.1:5000/api/get-token',
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {'Content-Type': 'application/json', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios', 'user_agent': 'iOS/10.3'},
|
||||||
|
'json': {'sign': '958a05393efef0ac7c0fb80a7eac45e24fd40c27'}
|
||||||
|
},
|
||||||
|
'extract': [
|
||||||
|
{'token': 'content.token'}
|
||||||
|
],
|
||||||
|
'validate': [
|
||||||
|
{'eq': ['status_code', 200]},
|
||||||
|
{'eq': ['headers.Content-Type', 'application/json']},
|
||||||
|
{'eq': ['content.success', True]}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'name': '/api/users/1000',
|
||||||
|
'request': {
|
||||||
|
'url': 'http://127.0.0.1:5000/api/users/1000',
|
||||||
|
'method': 'POST',
|
||||||
|
'headers': {'Content-Type': 'application/json', 'device_sn': 'FwgRiO7CNA50DSU','token': '$token'}, 'json': {'name': 'user1', 'password': '123456'}
|
||||||
|
},
|
||||||
|
'validate': [
|
||||||
|
{'eq': ['status_code', 201]},
|
||||||
|
{'eq': ['headers.Content-Type', 'application/json']},
|
||||||
|
{'eq': ['content.success', True]},
|
||||||
|
{'eq': ['content.msg', 'user created successfully.']}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
task_suite = task.TaskSuite(testsets)
|
||||||
|
self.assertEqual(task_suite.countTestCases(), 2)
|
||||||
for suite in task_suite:
|
for suite in task_suite:
|
||||||
for testcase in suite:
|
for testcase in suite:
|
||||||
self.assertIsInstance(testcase, task.TestCase)
|
self.assertIsInstance(testcase, task.TestCase)
|
||||||
|
|||||||
@@ -738,3 +738,32 @@ class TestcaseParserUnittest(unittest.TestCase):
|
|||||||
{"var3": "val3"},
|
{"var3": "val3"},
|
||||||
merged_extractors
|
merged_extractors
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_is_testsets(self):
|
||||||
|
data_structure = "path/to/file"
|
||||||
|
self.assertFalse(testcase.is_testsets(data_structure))
|
||||||
|
data_structure = ["path/to/file1", "path/to/file2"]
|
||||||
|
self.assertFalse(testcase.is_testsets(data_structure))
|
||||||
|
|
||||||
|
data_structure = {
|
||||||
|
"name": "desc1",
|
||||||
|
"config": {},
|
||||||
|
"api": {},
|
||||||
|
"testcases": ["testcase11", "testcase12"]
|
||||||
|
}
|
||||||
|
self.assertTrue(data_structure)
|
||||||
|
data_structure = [
|
||||||
|
{
|
||||||
|
"name": "desc1",
|
||||||
|
"config": {},
|
||||||
|
"api": {},
|
||||||
|
"testcases": ["testcase11", "testcase12"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "desc2",
|
||||||
|
"config": {},
|
||||||
|
"api": {},
|
||||||
|
"testcases": ["testcase21", "testcase22"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.assertTrue(data_structure)
|
||||||
|
|||||||
Reference in New Issue
Block a user