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