diff --git a/httprunner/cli.py b/httprunner/cli.py index 6829a57e..094fc8ff 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -7,7 +7,7 @@ if len(sys.argv) >= 2 and sys.argv[1] == "locusts": try: from gevent import monkey monkey.patch_ssl() - from locust.main import main + from locust.main import main as _ except ImportError: msg = """ Locust is not installed, install first and try again. @@ -20,7 +20,7 @@ $ pip install locustio from loguru import logger from httprunner import __description__, __version__ -from httprunner.api import HttpRunner +from httprunner.v3.api import HttpRunner from httprunner.ext.har2case import init_har2case_parser, main_har2case from httprunner.ext.scaffold import init_parser_scaffold, main_scaffold from httprunner.ext.locusts import init_parser_locusts, main_locusts diff --git a/httprunner/v3/response.py b/httprunner/v3/response.py index d4546962..81404b80 100644 --- a/httprunner/v3/response.py +++ b/httprunner/v3/response.py @@ -24,36 +24,7 @@ class ResponseObject(object): "headers": resp_obj.headers, "body": resp_obj.json() } - - def validate(self, - validators: Validators, - variables_mapping: VariablesMapping = None, - functions_mapping: FunctionsMapping = None) -> NoReturn: - - for v in validators: - u_validator = uniform_validator(v) - field = u_validator["check"] - assert_method = u_validator["assert"] - expect_value = u_validator["expect"] - actual_value = jmespath.search(field, self.resp_obj_meta) - - msg = f"assert {field} {assert_method} {expect_value}" - - assert_func = get_mapping_function(assert_method, functions_mapping) - actual_value = parse_string_value(actual_value) - # parse expected value with config/teststep/extracted variables - expect_value = parse_data(expect_value, variables_mapping, functions_mapping) - - try: - assert_func(actual_value, expect_value) - msg += " - success" - logger.info(msg) - except AssertionError: - msg += " - fail" - logger.error(msg) - actual_type = type(actual_value).__name__ - expect_type = type(expect_value).__name__ - raise ValidationFailure(f"assert {field}: {actual_value}({actual_type}) {assert_method} {expect_value}({expect_type})") + self.validation_results = {} def extract(self, extractors: Dict[Text, Text]) -> Dict[Text, Any]: if not extractors: @@ -66,3 +37,71 @@ class ResponseObject(object): logger.info(f"extract mapping: {extract_mapping}") return extract_mapping + + def validate(self, + validators: Validators, + variables_mapping: VariablesMapping = None, + functions_mapping: FunctionsMapping = None) -> NoReturn: + + self.validation_results = {} + if not validators: + return + + validate_pass = True + 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"] + check_value = jmespath.search(check_item, self.resp_obj_meta) + check_value = parse_string_value(check_value) + + # comparator + assert_method = u_validator["assert"] + assert_func = get_mapping_function(assert_method, functions_mapping) + + # expect item + expect_item = u_validator["expect"] + # parse expected value with config/teststep/extracted variables + expect_value = parse_data(expect_item, variables_mapping, functions_mapping) + + validate_msg = f"assert {check_item} {assert_method} {expect_value}({type(expect_value).__name__})" + + validator_dict = { + "comparator": assert_method, + "check": check_item, + "check_value": check_value, + "expect": expect_item, + "expect_value": expect_value + } + + try: + assert_func(check_value, expect_value) + validate_msg += "\t==> pass" + logger.info(validate_msg) + validator_dict["check_result"] = "pass" + except AssertionError: + validate_pass = False + validator_dict["check_result"] = "fail" + validate_msg += "\t==> fail" + validate_msg += "\n{}({}) {} {}({})".format( + check_value, + type(check_value).__name__, + assert_method, + expect_value, + type(expect_value).__name__ + ) + logger.error(validate_msg) + failures.append(validate_msg) + + self.validation_results["validate_extractor"].append(validator_dict) + + if not validate_pass: + failures_string = "\n".join([failure for failure in failures]) + raise ValidationFailure(failures_string) diff --git a/httprunner/v3/runner.py b/httprunner/v3/runner.py index bbf2a30f..1225e795 100644 --- a/httprunner/v3/runner.py +++ b/httprunner/v3/runner.py @@ -3,6 +3,7 @@ from typing import List from loguru import logger from httprunner.client import HttpSession +from httprunner.v3.exceptions import ValidationFailure from httprunner.v3.parser import build_url, parse_data, parse_variables_mapping from httprunner.v3.response import ResponseObject from httprunner.v3.schema import TestsConfig, TestStep, VariablesMapping, TestCase @@ -14,6 +15,7 @@ class TestCaseRunner(object): teststeps: List[TestStep] = [] session: HttpSession = None meta_datas: List = [] + validation_results: List = [] def init(self, testcase: TestCase) -> "TestCaseRunner": self.config = testcase.config @@ -59,7 +61,12 @@ class TestCaseRunner(object): # validate validators = step.validators - resp_obj.validate(validators, variables_mapping, self.config.functions) + try: + resp_obj.validate(validators, variables_mapping, self.config.functions) + except ValidationFailure: + raise + finally: + self.validation_results = resp_obj.validation_results return extract_mapping @@ -79,6 +86,7 @@ class TestCaseRunner(object): # save extracted variables to session variables session_variables.update(extract_mapping) # save request & response meta data + self.session.meta_data["validators"] = self.validation_results self.session.meta_data["name"] = step.name self.meta_datas.append(self.session.meta_data)