Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix skip错误和增加mark功能和增加meta功能收集用例 #1733

Open
wants to merge 6 commits into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 25 additions & 2 deletions httprunner/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 0 additions & 1 deletion httprunner/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:]
53 changes: 43 additions & 10 deletions httprunner/make.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
""" 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 }}
Expand All @@ -36,7 +38,7 @@
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 %}

Expand All @@ -50,9 +52,20 @@
{% 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)

Expand All @@ -61,8 +74,15 @@ def test_start(self, param):
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()

Expand Down Expand Up @@ -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})"

Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)


Expand Down
60 changes: 39 additions & 21 deletions httprunner/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -194,7 +211,6 @@ def validate(
variables_mapping: VariablesMapping = None,
functions_mapping: FunctionsMapping = None,
):

variables_mapping = variables_mapping or {}
functions_mapping = functions_mapping or {}

Expand All @@ -206,12 +222,10 @@ def validate(
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:
Expand All @@ -229,7 +243,11 @@ def validate(

# 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"]
Expand Down
19 changes: 17 additions & 2 deletions httprunner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,16 @@ def __run_step_request(self, step: TStep) -> StepData:
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

Expand Down Expand Up @@ -446,7 +453,14 @@ def test_start(self, param: Dict = None) -> "HttpRunner":
# 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}"
)
Expand All @@ -456,5 +470,6 @@ def test_start(self, param: Dict = None) -> "HttpRunner":
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}")
8 changes: 8 additions & 0 deletions httprunner/testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ def assert_type_match(
{"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
Expand Down