mirror of
https://github.com/httprunner/httprunner.git
synced 2026-08-28 03:30:01 +08:00
+21
-7
@@ -1,15 +1,29 @@
|
|||||||
# Release History
|
# Release History
|
||||||
|
|
||||||
## 2.0.1 (2019.01.18)
|
## 2.0.2 (2019-01-21)
|
||||||
|
|
||||||
Bugfixes:
|
**Bugfixes**
|
||||||
|
|
||||||
- override current teststep variables with former testcase output variables;
|
- each teststeps in one testcase share the same session
|
||||||
- make compatible with testcase name is empty;
|
- fix duplicate API definition output
|
||||||
- skip undefined variable when parsing string content;
|
|
||||||
- add request method in report.
|
|
||||||
|
|
||||||
## 2.0.0 (2019.01.01)
|
**Improvements**
|
||||||
|
|
||||||
|
- display result from hook functions in DEBUG level log
|
||||||
|
- change log level of "Variables & Output" to INFO
|
||||||
|
- print Invalid testcase path or testcases
|
||||||
|
- print testcase output in INFO level log
|
||||||
|
|
||||||
|
## 2.0.1 (2019-01-18)
|
||||||
|
|
||||||
|
**Bugfixes**
|
||||||
|
|
||||||
|
- override current teststep variables with former testcase output variables
|
||||||
|
- make compatible with testcase name is empty
|
||||||
|
- skip undefined variable when parsing string content
|
||||||
|
- add back request method in report
|
||||||
|
|
||||||
|
## 2.0.0 (2019-01-01)
|
||||||
|
|
||||||
- Massive Refactor and Simplification
|
- Massive Refactor and Simplification
|
||||||
- Redesign testcase structure
|
- Redesign testcase structure
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
__title__ = 'HttpRunner'
|
__title__ = 'HttpRunner'
|
||||||
__description__ = 'One-stop solution for HTTP(S) testing.'
|
__description__ = 'One-stop solution for HTTP(S) testing.'
|
||||||
__url__ = 'https://github.com/HttpRunner/HttpRunner'
|
__url__ = 'https://github.com/HttpRunner/HttpRunner'
|
||||||
__version__ = '2.0.1'
|
__version__ = '2.0.2'
|
||||||
__author__ = 'debugtalk'
|
__author__ = 'debugtalk'
|
||||||
__author_email__ = 'mail@debugtalk.com'
|
__author_email__ = 'mail@debugtalk.com'
|
||||||
__license__ = 'Apache-2.0'
|
__license__ = 'Apache-2.0'
|
||||||
|
|||||||
+2
-5
@@ -145,10 +145,7 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
summary["success"] &= testcase_summary["success"]
|
summary["success"] &= testcase_summary["success"]
|
||||||
testcase_summary["name"] = testcase.config.get("name")
|
testcase_summary["name"] = testcase.config.get("name")
|
||||||
|
testcase_summary["in_out"] = utils.get_testcase_io(testcase)
|
||||||
in_out = utils.get_testcase_io(testcase)
|
|
||||||
utils.print_io(in_out)
|
|
||||||
testcase_summary["in_out"] = in_out
|
|
||||||
|
|
||||||
report.aggregate_stat(summary["stat"]["teststeps"], testcase_summary["stat"])
|
report.aggregate_stat(summary["stat"]["teststeps"], testcase_summary["stat"])
|
||||||
report.aggregate_stat(summary["time"], testcase_summary["time"])
|
report.aggregate_stat(summary["time"], testcase_summary["time"])
|
||||||
@@ -233,7 +230,7 @@ class HttpRunner(object):
|
|||||||
elif validator.is_testcases(path_or_tests):
|
elif validator.is_testcases(path_or_tests):
|
||||||
return self.run_tests(path_or_tests)
|
return self.run_tests(path_or_tests)
|
||||||
else:
|
else:
|
||||||
raise exceptions.ParamsError("invalid testcase path or testcases.")
|
raise exceptions.ParamsError("Invalid testcase path or testcases: {}".format(path_or_tests))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def summary(self):
|
def summary(self):
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ class HttpSession(requests.Session):
|
|||||||
:param cert: (optional)
|
:param cert: (optional)
|
||||||
if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||||
"""
|
"""
|
||||||
|
self.init_meta_data()
|
||||||
|
|
||||||
# record test name
|
# record test name
|
||||||
self.meta_data["name"] = name
|
self.meta_data["name"] = name
|
||||||
|
|
||||||
|
|||||||
+13
-7
@@ -613,17 +613,23 @@ def load_api_folder(api_folder_path):
|
|||||||
if isinstance(api_items, list):
|
if isinstance(api_items, list):
|
||||||
for api_item in api_items:
|
for api_item in api_items:
|
||||||
key, api_dict = api_item.popitem()
|
key, api_dict = api_item.popitem()
|
||||||
api_id = api_dict.get("id")
|
api_id = api_dict.get("id") or api_dict.get("def") or api_dict.get("name")
|
||||||
if api_id in api_definition_mapping:
|
if key != "api" or not api_id:
|
||||||
logger.log_warning("API definition duplicated: {}".format(api_id))
|
raise exceptions.ParamsError(
|
||||||
|
"Invalid API defined in {}".format(api_file_path))
|
||||||
|
|
||||||
api_definition_mapping[api_id] = api_dict
|
if api_id in api_definition_mapping:
|
||||||
|
raise exceptions.ParamsError(
|
||||||
|
"Duplicated API ({}) defined in {}".format(api_id, api_file_path))
|
||||||
|
else:
|
||||||
|
api_definition_mapping[api_id] = api_dict
|
||||||
|
|
||||||
elif isinstance(api_items, dict):
|
elif isinstance(api_items, dict):
|
||||||
if api_file_path in api_definition_mapping:
|
if api_file_path in api_definition_mapping:
|
||||||
logger.log_warning("API definition duplicated: {}".format(api_file_path))
|
raise exceptions.ParamsError(
|
||||||
|
"Duplicated API defined: {}".format(api_file_path))
|
||||||
api_definition_mapping[api_file_path] = api_items
|
else:
|
||||||
|
api_definition_mapping[api_file_path] = api_items
|
||||||
|
|
||||||
return api_definition_mapping
|
return api_definition_mapping
|
||||||
|
|
||||||
|
|||||||
+13
-13
@@ -140,10 +140,14 @@ class Runner(object):
|
|||||||
# format 1
|
# format 1
|
||||||
# {"var": "${func()}"}
|
# {"var": "${func()}"}
|
||||||
var_name, hook_content = list(action.items())[0]
|
var_name, hook_content = list(action.items())[0]
|
||||||
logger.log_debug("assignment with hook: {} = {}".format(var_name, hook_content))
|
hook_content_eval = self.session_context.eval_content(hook_content)
|
||||||
|
logger.log_debug(
|
||||||
|
"assignment with hook: {} = {} => {}".format(
|
||||||
|
var_name, hook_content, hook_content_eval
|
||||||
|
)
|
||||||
|
)
|
||||||
self.session_context.update_test_variables(
|
self.session_context.update_test_variables(
|
||||||
var_name,
|
var_name, hook_content_eval
|
||||||
self.session_context.eval_content(hook_content)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# format 2
|
# format 2
|
||||||
@@ -282,11 +286,9 @@ class Runner(object):
|
|||||||
"""
|
"""
|
||||||
self.meta_datas = []
|
self.meta_datas = []
|
||||||
config = testcase_dict.get("config", {})
|
config = testcase_dict.get("config", {})
|
||||||
base_url = config.get("base_url")
|
|
||||||
|
|
||||||
# each testcase should have individual session.
|
# each teststeps in one testcase (YAML/JSON) share the same session.
|
||||||
http_client_session = self.http_client_session.__class__(base_url)
|
test_runner = Runner(config, self.functions, self.http_client_session)
|
||||||
test_runner = Runner(config, self.functions, http_client_session)
|
|
||||||
|
|
||||||
tests = testcase_dict.get("teststeps", [])
|
tests = testcase_dict.get("teststeps", [])
|
||||||
|
|
||||||
@@ -309,7 +311,9 @@ class Runner(object):
|
|||||||
_meta_datas = test_runner.meta_datas
|
_meta_datas = test_runner.meta_datas
|
||||||
self.meta_datas.append(_meta_datas)
|
self.meta_datas.append(_meta_datas)
|
||||||
|
|
||||||
self.session_context.update_session_variables(test_runner.extract_sessions())
|
self.session_context.update_session_variables(
|
||||||
|
test_runner.extract_output(test_runner.output)
|
||||||
|
)
|
||||||
|
|
||||||
def run_test(self, test_dict):
|
def run_test(self, test_dict):
|
||||||
""" run single teststep of testcase.
|
""" run single teststep of testcase.
|
||||||
@@ -360,11 +364,6 @@ class Runner(object):
|
|||||||
finally:
|
finally:
|
||||||
self.meta_datas = self.__get_test_data()
|
self.meta_datas = self.__get_test_data()
|
||||||
|
|
||||||
def extract_sessions(self):
|
|
||||||
"""
|
|
||||||
"""
|
|
||||||
return self.extract_output(self.output)
|
|
||||||
|
|
||||||
def extract_output(self, output_variables_list):
|
def extract_output(self, output_variables_list):
|
||||||
""" extract output variables
|
""" extract output variables
|
||||||
"""
|
"""
|
||||||
@@ -381,4 +380,5 @@ class Runner(object):
|
|||||||
|
|
||||||
output[variable] = variables_mapping[variable]
|
output[variable] = variables_mapping[variable]
|
||||||
|
|
||||||
|
utils.print_info(output)
|
||||||
return output
|
return output
|
||||||
|
|||||||
+38
-50
@@ -433,13 +433,12 @@ def extend_variables(raw_variables, override_variables):
|
|||||||
|
|
||||||
|
|
||||||
def get_testcase_io(testcase):
|
def get_testcase_io(testcase):
|
||||||
""" get testcase input(variables) and output.
|
""" get and print testcase input(variables) and output.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
testcase (unittest.suite.TestSuite): corresponding to one YAML/JSON file, it has been set two attributes:
|
testcase (unittest.suite.TestSuite): corresponding to one YAML/JSON file, it has been set two attributes:
|
||||||
config: parsed config block
|
config: parsed config block
|
||||||
runner: initialized runner.Runner() with config
|
runner: initialized runner.Runner() with config
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: input(variables) and output mapping.
|
dict: input(variables) and output mapping.
|
||||||
|
|
||||||
@@ -447,72 +446,61 @@ def get_testcase_io(testcase):
|
|||||||
test_runner = testcase.runner
|
test_runner = testcase.runner
|
||||||
variables = testcase.config.get("variables", {})
|
variables = testcase.config.get("variables", {})
|
||||||
output_list = testcase.config.get("output", [])
|
output_list = testcase.config.get("output", [])
|
||||||
|
output_mapping = test_runner.extract_output(output_list)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"in": variables,
|
"in": variables,
|
||||||
"out": test_runner.extract_output(output_list)
|
"out": output_mapping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def print_io(in_out):
|
def print_info(info_mapping):
|
||||||
""" print input(variables) and output.
|
""" print info in mapping.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
in_out (dict): input(variables) and output mapping.
|
info_mapping (dict): input(variables) or output mapping.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>>> in_out = {
|
>>> info_mapping = {
|
||||||
"in": {
|
"var_a": "hello",
|
||||||
"var_a": "hello",
|
"var_b": "world"
|
||||||
"var_b": "world"
|
|
||||||
},
|
|
||||||
"out": {
|
|
||||||
"status_code": 500
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
>>> print_io(in_out)
|
>>> info_mapping = {
|
||||||
================== Variables & Output ==================
|
"status_code": 500
|
||||||
Type | Variable : Value
|
}
|
||||||
------ | ---------------- : ---------------------------
|
>>> print_info(info_mapping)
|
||||||
Var | var_a : hello
|
==================== Output ====================
|
||||||
Var | var_b : world
|
Key : Value
|
||||||
|
---------------- : ----------------------------
|
||||||
Out | status_code : 500
|
var_a : hello
|
||||||
--------------------------------------------------------
|
var_b : world
|
||||||
|
------------------------------------------------
|
||||||
|
|
||||||
"""
|
"""
|
||||||
content_format = "{:<6} | {:<16} : {:<}\n"
|
if not info_mapping:
|
||||||
content = "\n================== Variables & Output ==================\n"
|
return
|
||||||
content += content_format.format("Type", "Variable", "Value")
|
|
||||||
content += content_format.format("-" * 6, "-" * 16, "-" * 27)
|
|
||||||
|
|
||||||
def prepare_content(var_type, in_out):
|
content_format = "{:<16} : {:<}\n"
|
||||||
content = ""
|
content = "\n==================== Output ====================\n"
|
||||||
for variable, value in in_out.items():
|
content += content_format.format("Variable", "Value")
|
||||||
if isinstance(value, (tuple, collections.deque)):
|
content += content_format.format("-" * 16, "-" * 29)
|
||||||
continue
|
|
||||||
elif isinstance(value, (dict, list)):
|
|
||||||
value = json.dumps(value)
|
|
||||||
|
|
||||||
if is_py2:
|
for key, value in info_mapping.items():
|
||||||
if isinstance(variable, unicode):
|
if isinstance(value, (tuple, collections.deque)):
|
||||||
variable = variable.encode("utf-8")
|
continue
|
||||||
if isinstance(value, unicode):
|
elif isinstance(value, (dict, list)):
|
||||||
value = value.encode("utf-8")
|
value = json.dumps(value)
|
||||||
|
|
||||||
content += content_format.format(var_type, variable, value)
|
if is_py2:
|
||||||
|
if isinstance(key, unicode):
|
||||||
|
key = key.encode("utf-8")
|
||||||
|
if isinstance(value, unicode):
|
||||||
|
value = value.encode("utf-8")
|
||||||
|
|
||||||
return content
|
content += content_format.format(key, value)
|
||||||
|
|
||||||
_in = in_out["in"]
|
content += "-" * 48 + "\n"
|
||||||
_out = in_out["out"]
|
logger.log_info(content)
|
||||||
|
|
||||||
content += prepare_content("Var", _in)
|
|
||||||
content += "\n"
|
|
||||||
content += prepare_content("Out", _out)
|
|
||||||
content += "-" * 56 + "\n"
|
|
||||||
|
|
||||||
logger.log_debug(content)
|
|
||||||
|
|
||||||
|
|
||||||
def create_scaffold(project_name):
|
def create_scaffold(project_name):
|
||||||
|
|||||||
Reference in New Issue
Block a user