mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-23 08:33:45 +08:00
fix skip错误和增加mark功能和增加meta功能收集用例 (#1733)
* fix skip错误和增加mark功能和增加meta功能收集用例 * 增加自定义验证器 * 在allure报告中增加运行日志文件 * 测试用例元数据收集增加用例文件路径 * 增加公司的全链路追踪ID * allure测试报告增加用例repo地址link
This commit is contained in:
@@ -217,12 +217,35 @@ def ensure_testcase_v3_api(api_content: Dict) -> Dict:
|
||||
|
||||
def ensure_testcase_v3(test_content: Dict) -> Dict:
|
||||
logger.info("ensure compatibility with testcase format v2")
|
||||
|
||||
v3_content = {"config": test_content["config"], "teststeps": []}
|
||||
logger.info(f"test_content: {test_content}")
|
||||
v3_content = {"config": test_content["config"], "teststeps": [], "meta": {"path": ""}}
|
||||
|
||||
if "teststeps" not in test_content:
|
||||
logger.error(f"Miss teststeps: {test_content}")
|
||||
sys.exit(1)
|
||||
|
||||
# 分析test_content["config"]中的config,如果没有meta
|
||||
if "meta" not in test_content['config']:
|
||||
logger.error(f"Miss meta test_file: {test_content['config']['path']}")
|
||||
logger.error(f"file config content : {test_content['config']}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
# 判断mata中是否有author,description,service,controller没有就抛错
|
||||
if "author" not in test_content['config']['meta']:
|
||||
logger.error(f"Miss author: {test_content}")
|
||||
sys.exit(1)
|
||||
if "description" not in test_content['config']['meta']:
|
||||
logger.error(f"Miss description: {test_content}")
|
||||
sys.exit(1)
|
||||
if "service" not in test_content['config']['meta']:
|
||||
logger.error(f"Miss service: {test_content}")
|
||||
sys.exit(1)
|
||||
if "controller" not in test_content['config']['meta']:
|
||||
logger.error(f"Miss controller: {test_content}")
|
||||
sys.exit(1)
|
||||
|
||||
v3_content["meta"] = test_content['config']['meta']
|
||||
v3_content["meta"]["path"] = convert_relative_project_root_dir(test_content['config']['path'])
|
||||
|
||||
if not isinstance(test_content["teststeps"], list):
|
||||
logger.error(
|
||||
|
||||
@@ -443,5 +443,4 @@ def convert_relative_project_root_dir(abs_path: Text) -> Text:
|
||||
f"abs_path: {abs_path}\n"
|
||||
f"project_meta.RootDir: {_project_meta.RootDir}"
|
||||
)
|
||||
|
||||
return abs_path[len(_project_meta.RootDir) + 1:]
|
||||
|
||||
@@ -25,6 +25,8 @@ pytest_files_made_cache_mapping: Dict[Text, Text] = {}
|
||||
""" save generated pytest files to run, except referenced testcase
|
||||
"""
|
||||
pytest_files_run_set: Set = set()
|
||||
"""用来记录make之后的所有case 元数据,做统计使用"""
|
||||
pytest_files_meta_cases_list: List = []
|
||||
|
||||
__TEMPLATE__ = jinja2.Template(
|
||||
"""# NOTE: Generated By HttpRunner v{{ version }}
|
||||
@@ -36,7 +38,7 @@ from pathlib import Path
|
||||
sys.path.insert(0, str(Path(__file__){% for _ in range(diff_levels) %}.parent{% endfor %}))
|
||||
{% endif %}
|
||||
|
||||
{% if parameters or skip %}
|
||||
{% if parameters or skip or marks %}
|
||||
import pytest
|
||||
{% endif %}
|
||||
|
||||
@@ -50,9 +52,20 @@ from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
||||
{% endfor %}
|
||||
|
||||
class {{ class_name }}(HttpRunner):
|
||||
{% if parameters and skip %}
|
||||
{% if parameters and skip and marks%}
|
||||
@pytest.mark.parametrize("param", Parameters({{parameters}}))
|
||||
@pytest.mark.skip(reason={{ skip }})
|
||||
@pytest.mark.skip(reason="{{ skip }}")
|
||||
{% for mark in marks %}
|
||||
@pytest.mark.{{ mark }}
|
||||
{% endfor %}
|
||||
def test_start(self, param):
|
||||
super().test_start(param)
|
||||
|
||||
{% elif parameters and marks %}
|
||||
@pytest.mark.parametrize("param", Parameters({{parameters}}))
|
||||
{% for mark in marks %}
|
||||
@pytest.mark.{{ mark }}
|
||||
{% endfor %}
|
||||
def test_start(self, param):
|
||||
super().test_start(param)
|
||||
|
||||
@@ -61,8 +74,15 @@ class {{ class_name }}(HttpRunner):
|
||||
def test_start(self, param):
|
||||
super().test_start(param)
|
||||
|
||||
{% elif marks %}
|
||||
{% for mark in marks %}
|
||||
@pytest.mark.{{ mark }}
|
||||
{% endfor %}
|
||||
def test_start(self):
|
||||
super().test_start()
|
||||
|
||||
{% elif skip %}
|
||||
@pytest.mark.skip(reason={{ skip }})
|
||||
@pytest.mark.skip(reason="{{ skip }}")
|
||||
def test_start(self):
|
||||
super().test_start()
|
||||
|
||||
@@ -344,10 +364,18 @@ def make_teststep_chain_style(teststep: Dict) -> Text:
|
||||
expect = f'"{expect}"'
|
||||
|
||||
message = validator["message"]
|
||||
if message:
|
||||
step_info += f".assert_{assert_method}({check}, {expect}, '{message}')"
|
||||
|
||||
if assert_method == "custom":
|
||||
custom_comparator = validator['custom_comparator']
|
||||
if message:
|
||||
step_info += f".assert_{assert_method}('{custom_comparator}',{check}, {expect}, '{message}')"
|
||||
else:
|
||||
step_info += f".assert_{assert_method}('{custom_comparator}',{check}, {expect})"
|
||||
else:
|
||||
step_info += f".assert_{assert_method}({check}, {expect})"
|
||||
if message:
|
||||
step_info += f".assert_{assert_method}({check}, {expect}, '{message}')"
|
||||
else:
|
||||
step_info += f".assert_{assert_method}({check}, {expect})"
|
||||
|
||||
return f"Step({step_info})"
|
||||
|
||||
@@ -356,7 +384,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
|
||||
"""convert valid testcase dict to pytest file path"""
|
||||
# ensure compatibility with testcase format v2
|
||||
testcase = ensure_testcase_v3(testcase)
|
||||
|
||||
|
||||
pytest_files_meta_cases_list.append(testcase['meta'])
|
||||
# validate testcase format
|
||||
load_testcase(testcase)
|
||||
|
||||
@@ -441,10 +470,10 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
|
||||
"teststeps_chain_style": [
|
||||
make_teststep_chain_style(step) for step in teststeps
|
||||
],
|
||||
"marks": config.get("marks", []),
|
||||
}
|
||||
logger.info(make_config_skip(config))
|
||||
content = __TEMPLATE__.render(data)
|
||||
|
||||
# ensure new file's directory exists
|
||||
dir_path = os.path.dirname(testcase_python_abs_path)
|
||||
if not os.path.exists(dir_path):
|
||||
@@ -526,7 +555,6 @@ def __make(tests_path: Text):
|
||||
tests_path: should be in absolute path
|
||||
|
||||
"""
|
||||
logger.info(f"make path: {tests_path}")
|
||||
test_files = []
|
||||
if os.path.isdir(tests_path):
|
||||
files_list = load_folder_files(tests_path)
|
||||
@@ -624,6 +652,11 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
|
||||
pytest_files_format_list = pytest_files_made_cache_mapping.keys()
|
||||
format_pytest_with_black(*pytest_files_format_list)
|
||||
|
||||
# 打印收集到的case数据
|
||||
logger.info(f"pytest_files_meta_cases_list: {pytest_files_meta_cases_list}")
|
||||
import json
|
||||
with open('pytest_files_meta_cases_list.json', 'w') as f:
|
||||
json.dump(pytest_files_meta_cases_list, f)
|
||||
return list(pytest_files_run_set)
|
||||
|
||||
|
||||
|
||||
@@ -88,30 +88,47 @@ def uniform_validator(validator):
|
||||
# format2
|
||||
comparator = list(validator.keys())[0]
|
||||
compare_values = validator[comparator]
|
||||
|
||||
if not isinstance(compare_values, list) or len(compare_values) not in [2, 3]:
|
||||
raise ParamsError(f"invalid validator: {validator}")
|
||||
|
||||
check_item = compare_values[0]
|
||||
expect_value = compare_values[1]
|
||||
if len(compare_values) == 3:
|
||||
message = compare_values[2]
|
||||
if comparator == "custom":
|
||||
custom_comparator = compare_values[0]
|
||||
check_item = compare_values[1]
|
||||
expect_value = compare_values[2]
|
||||
if len(compare_values) == 4:
|
||||
message = compare_values[3]
|
||||
else:
|
||||
# len(compare_values) == 2
|
||||
message = ""
|
||||
else:
|
||||
# len(compare_values) == 2
|
||||
message = ""
|
||||
if not isinstance(compare_values, list) or len(compare_values) not in [2, 3]:
|
||||
raise ParamsError(f"invalid validator: {validator}")
|
||||
|
||||
check_item = compare_values[0]
|
||||
expect_value = compare_values[1]
|
||||
if len(compare_values) == 3:
|
||||
message = compare_values[2]
|
||||
else:
|
||||
# len(compare_values) == 2
|
||||
message = ""
|
||||
|
||||
else:
|
||||
raise ParamsError(f"invalid validator: {validator}")
|
||||
|
||||
# uniform comparator, e.g. lt => less_than, eq => equals
|
||||
assert_method = get_uniform_comparator(comparator)
|
||||
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"assert": assert_method,
|
||||
"message": message,
|
||||
}
|
||||
if assert_method == "custom":
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"assert": assert_method,
|
||||
"message": message,
|
||||
"custom_comparator": custom_comparator,
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"check": check_item,
|
||||
"expect": expect_value,
|
||||
"assert": assert_method,
|
||||
"message": message,
|
||||
}
|
||||
|
||||
|
||||
class ResponseObject(object):
|
||||
@@ -194,7 +211,6 @@ class ResponseObject(object):
|
||||
variables_mapping: VariablesMapping = None,
|
||||
functions_mapping: FunctionsMapping = None,
|
||||
):
|
||||
|
||||
variables_mapping = variables_mapping or {}
|
||||
functions_mapping = functions_mapping or {}
|
||||
|
||||
@@ -206,12 +222,10 @@ class ResponseObject(object):
|
||||
failures = []
|
||||
|
||||
for v in validators:
|
||||
|
||||
if "validate_extractor" not in self.validation_results:
|
||||
self.validation_results["validate_extractor"] = []
|
||||
|
||||
u_validator = uniform_validator(v)
|
||||
|
||||
# check item
|
||||
check_item = u_validator["check"]
|
||||
if "$" in check_item:
|
||||
@@ -229,7 +243,11 @@ class ResponseObject(object):
|
||||
|
||||
# comparator
|
||||
assert_method = u_validator["assert"]
|
||||
assert_func = get_mapping_function(assert_method, functions_mapping)
|
||||
if assert_method != 'custom':
|
||||
assert_func = get_mapping_function(assert_method, functions_mapping)
|
||||
else:
|
||||
assert_method = u_validator["custom_comparator"]
|
||||
assert_func = get_mapping_function(assert_method, functions_mapping)
|
||||
|
||||
# expect item
|
||||
expect_item = u_validator["expect"]
|
||||
|
||||
@@ -145,9 +145,16 @@ class HttpRunner(object):
|
||||
parsed_request_dict = parse_data(
|
||||
request_dict, step.variables, self.__project_meta.functions
|
||||
)
|
||||
case_id = f"HRUN-{self.__case_id}-{str(int(time.time() * 1000))[-6:]}"
|
||||
# 增加x-trace-id
|
||||
parsed_request_dict["headers"].setdefault(
|
||||
"x-trace-id",
|
||||
case_id,
|
||||
)
|
||||
|
||||
parsed_request_dict["headers"].setdefault(
|
||||
"HRUN-Request-ID",
|
||||
f"HRUN-{self.__case_id}-{str(int(time.time() * 1000))[-6:]}",
|
||||
case_id,
|
||||
)
|
||||
step.variables["request"] = parsed_request_dict
|
||||
|
||||
@@ -446,7 +453,14 @@ class HttpRunner(object):
|
||||
# update allure report meta
|
||||
allure.dynamic.title(self.__config.name)
|
||||
allure.dynamic.description(f"TestCase ID: {self.__case_id}")
|
||||
|
||||
git_repo = self.__project_meta.env.get("git_repo", '')
|
||||
file_ext = self.__project_meta.env.get("file_ext", '')
|
||||
if git_repo != '' and file_ext != '':
|
||||
from httprunner.loader import convert_relative_project_root_dir
|
||||
# 由于运行时加载的是py文件,所以这里link的时候得做一下替换
|
||||
link = git_repo + convert_relative_project_root_dir(self.__config.path)[:-8] + '.' +file_ext
|
||||
allure.dynamic.link(link)
|
||||
|
||||
logger.info(
|
||||
f"Start to run testcase: {self.__config.name}, TestCase ID: {self.__case_id}"
|
||||
)
|
||||
@@ -456,5 +470,6 @@ class HttpRunner(object):
|
||||
TestCase(config=self.__config, teststeps=self.__teststeps)
|
||||
)
|
||||
finally:
|
||||
allure.attach.file(self.__log_path, name="log", attachment_type=allure.attachment_type.TEXT)
|
||||
logger.remove(log_handler)
|
||||
logger.info(f"generate testcase log: {self.__log_path}")
|
||||
|
||||
@@ -213,6 +213,14 @@ class StepRequestValidation(object):
|
||||
{"type_match": [jmes_path, expected_value, message]}
|
||||
)
|
||||
return self
|
||||
|
||||
def assert_custom(
|
||||
self, custom_comparator, jmes_path: Text, expected_value: Any, message: Text = ""
|
||||
) -> "StepRequestValidation":
|
||||
self.__step_context.validators.append(
|
||||
{"custom": [custom_comparator, jmes_path, expected_value, message]}
|
||||
)
|
||||
return self
|
||||
|
||||
def perform(self) -> TStep:
|
||||
return self.__step_context
|
||||
|
||||
Reference in New Issue
Block a user