Exemplo n.º 1
0
def parse_validator(validator):
    """ parse validator, validator maybe in two format
    @param (dict) validator
        format1: this is kept for compatiblity with the previous versions.
            {"check": "status_code", "comparator": "eq", "expect": 201}
            {"check": "$resp_body_success", "comparator": "eq", "expect": True}
        format2: recommended new version
            {'eq': ['status_code', 201]}
            {'eq': ['$resp_body_success', True]}
    @return (dict) validator info
        {
            "check": "status_code",
            "expect": 201,
            "comparator": "eq"
        }
    """
    if not isinstance(validator, dict):
        raise exceptions.ParamsError("invalid validator: {}".format(validator))

    if "check" in validator and len(validator) > 1:
        # format1
        check_item = validator.get("check")

        if "expect" in validator:
            expect_value = validator.get("expect")
        elif "expected" in validator:
            expect_value = validator.get("expected")
        else:
            raise exceptions.ParamsError(
                "invalid validator: {}".format(validator))

        comparator = validator.get("comparator", "eq")

    elif len(validator) == 1:
        # format2
        comparator = list(validator.keys())[0]
        compare_values = validator[comparator]

        # 有断言备注的情况下,舍弃备注信息
        if isinstance(compare_values, list) and len(compare_values) > 2:
            compare_values.pop()

        if not isinstance(compare_values, list) or len(compare_values) != 2:
            raise exceptions.ParamsError(
                "invalid validator: {}".format(validator))

        check_item, expect_value = compare_values

    else:
        raise exceptions.ParamsError("invalid validator: {}".format(validator))

    return {
        "check": check_item,
        "expect": expect_value,
        "comparator": comparator
    }
Exemplo n.º 2
0
    def __getattr__(self, key):
        try:
            if key == "json":
                value = self.resp_obj.json()
            else:
                value = getattr(self.resp_obj, key)

            self.__dict__[key] = value
            return value
        except AttributeError:
            err_msg = "ResponseObject does not have attribute: {}".format(key)
            logger.log_error(err_msg)
            raise exceptions.ParamsError(err_msg)
Exemplo n.º 3
0
    def extract_field(self, field, key=None, context_obj=None):
        """ extract value from requests.Response.
        """
        if not isinstance(field, basestring):
            err_msg = u"Invalid extractor! => {}\n".format(field)
            logger.log_error(err_msg)
            raise exceptions.ParamsError(err_msg)

        # msg = "extract: {}".format(field)
        if key:
            msg = "【提取变量】: {}".format(field)
        else:
            msg = "【提取】: {}".format(field)

        sub_str_exp = None
        original_value = None
        # if text_extractor_regexp_compile.match(field):
        #     value = self._extract_field_with_regex(field)
        # else:
        #     original_value, sub_str_exp = self._extract_field_with_delimiter(field, context_obj=context_obj)
        try:
            original_value, sub_str_exp = self._extract_field_with_delimiter(
                field, context_obj=context_obj)
        except Exception as err:
            raise exceptions.ExtractFailure(err)
        if sub_str_exp:
            value = eval('original_value' + sub_str_exp)
        else:
            value = original_value

        if is_py2 and isinstance(value, unicode):
            value = value.encode("utf-8")

        if key:
            if sub_str_exp:
                msg += "  ==>  {0}  ==>  {1}  保存为变量 {2}".format(
                    original_value + sub_str_exp, value, key)
            else:
                msg += "  ==>  {0}  保存为变量 {1}".format(value, key)
        else:
            msg += "  ==>  {0}".format(value)
        logger.log_info(msg)

        return value
Exemplo n.º 4
0
Arquivo: utils.py Projeto: zmhtest/ATP
def override_mapping_list(variables, new_mapping):
    """ override variables with new mapping.

    Args:
        variables (list): variables list
            [
                {"var_a": 1},
                {"var_b": "world"}
            ]
        new_mapping (dict): overrided variables mapping
            {
                "var_a": "hello"
            }

    Returns:
        OrderedDict: overrided variables mapping.

    Examples:
        >>> variables = [
                {"var_a": 1},
                {"var_b": "world"}
            ]
        >>> new_mapping = {
                "var_a": "hello"
            }
        >>> override_mapping_list(variables, new_mapping)
            OrderedDict(
                {
                    "var_a": "hello",
                    "var_b": "world"
                }
            )

    """
    if isinstance(variables, list):
        variables_ordered_dict = convert_mappinglist_to_orderdict(variables)
    elif isinstance(variables, (OrderedDict, dict)):
        variables_ordered_dict = variables
    else:
        raise exceptions.ParamsError("variables error!")

    return update_ordered_dict(variables_ordered_dict, new_mapping)
Exemplo n.º 5
0
def _get_block_by_name(ref_call, ref_type):
    """ get test content by reference name.

    Args:
        ref_call (str): call function.
            e.g. api_v1_Account_Login_POST($UserName, $Password)
        ref_type (enum): "def-api" or "def-testcase"

    Returns:
        dict: api/testcase definition.

    Raises:
        exceptions.ParamsError: call args number is not equal to defined args number.

    """
    function_meta = parser.parse_function(ref_call)
    func_name = function_meta["func_name"]
    call_args = function_meta["args"]
    block = _get_test_definition(func_name, ref_type)
    def_args = block.get("function_meta", {}).get("args", [])

    if len(call_args) != len(def_args):
        err_msg = "{}: call args number is not equal to defined args number!\n".format(
            func_name)
        err_msg += "defined args: {}\n".format(def_args)
        err_msg += "reference args: {}".format(call_args)
        logger.log_error(err_msg)
        raise exceptions.ParamsError(err_msg)

    args_mapping = {}
    for index, item in enumerate(def_args):
        if call_args[index] == item:
            continue

        args_mapping[item] = call_args[index]

    if args_mapping:
        block = parser.substitute_variables(block, args_mapping)

    return block
Exemplo n.º 6
0
def parse_parameters(parameters, variables_mapping, functions_mapping):
    """ parse parameters and generate cartesian product.

    Args:
        parameters (list) parameters: parameter name and value in list
            parameter value may be in three types:
                (1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
                (2) call built-in parameterize function, "${parameterize(account.csv)}"
                (3) call custom function in debugtalk.py, "${gen_app_version()}"

        variables_mapping (dict): variables mapping loaded from debugtalk.py
        functions_mapping (dict): functions mapping loaded from debugtalk.py

    Returns:
        list: cartesian product list

    Examples:
        >>> parameters = [
            {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
            {"username-password": "******"},
            {"app_version": "${gen_app_version()}"}
        ]
        >>> parse_parameters(parameters)

    """
    parsed_parameters_list = []
    for parameter in parameters:
        parameter_name, parameter_content = list(parameter.items())[0]
        parameter_name_list = parameter_name.split("-")

        if isinstance(parameter_content, list):
            # (1) data list
            # e.g. {"app_version": ["2.8.5", "2.8.6"]}
            #       => [{"app_version": "2.8.5", "app_version": "2.8.6"}]
            # e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]}
            #       => [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}]
            parameter_content_list = []
            for parameter_item in parameter_content:
                if not isinstance(parameter_item, (list, tuple)):
                    # "2.8.5" => ["2.8.5"]
                    parameter_item = [parameter_item]

                # ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"}
                # ["username", "password"], ["user1", "111111"] => {"username": "******", "password": "******"}
                parameter_content_dict = dict(
                    zip(parameter_name_list, parameter_item))

                parameter_content_list.append(parameter_content_dict)
        else:
            # (2) & (3)
            parsed_parameter_content = parse_data(parameter_content,
                                                  variables_mapping,
                                                  functions_mapping)
            # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}]
            # e.g. [{"username": "******", "password": "******"}, {"username": "******", "password": "******"}]
            if not isinstance(parsed_parameter_content, list):
                raise exceptions.ParamsError("parameters syntax error!")

            parameter_content_list = [
                # get subset by parameter name
                {key: parameter_item[key]
                 for key in parameter_name_list}
                for parameter_item in parsed_parameter_content
            ]

        parsed_parameters_list.append(parameter_content_list)

    return utils.gen_cartesian_product(*parsed_parameters_list)
Exemplo n.º 7
0
    def run_test(self, teststep_dict):
        """ run single teststep.

        Args:
            teststep_dict (dict): teststep info
                {
                    "name": "teststep description",
                    "skip": "skip this test unconditionally",
                    "times": 3,
                    "variables": [],        # optional, override
                    "request": {
                        "url": "http://127.0.0.1:5000/api/users/1000",
                        "method": "POST",
                        "headers": {
                            "Content-Type": "application/json",
                            "authorization": "$authorization",
                            "random": "$random"
                        },
                        "body": '{"name": "user", "password": "******"}'
                    },
                    "extract": [],              # optional
                    "validate": [],             # optional
                    "setup_hooks": [],          # optional
                    "teardown_hooks": []        # optional
                }

        Raises:
            exceptions.ParamsError
            exceptions.ValidationFailure
            exceptions.ExtractFailure

        """
        self.teststep_dict = teststep_dict

        is_last = teststep_dict.get("is_last", None)
        case_name = teststep_dict.get("name", None)
        case_id = teststep_dict.get("case_id", None)
        logger.log_info("【开始执行用例】: ID_{0}, {1}".format(case_id, case_name))
        self.step_teardown_executed = False

        # check skip
        self._handle_skip_feature(teststep_dict)

        # prepare
        logger.log_info("-" * 12 + "【变量替换-开始】" + "-" * 12)
        extractors = teststep_dict.get("extract", []) or teststep_dict.get(
            "extractors", [])
        validators = teststep_dict.get("validate", []) or teststep_dict.get(
            "validators", [])
        self.step_parse_variable_pass = True
        self.running_hook = 'step_parse_variable'
        parsed_request = self.init_config(teststep_dict, level="teststep")
        self.context.update_teststep_variables_mapping("request",
                                                       parsed_request)
        logger.log_info("-" * 12 + "【变量替换-结束】" + "-" * 12)
        if self.variable_not_found:
            self.handle_teardown(fail_type='变量替换')
            raise exceptions.VariableNotFound

        if not self.step_parse_variable_pass:
            self.handle_teardown(fail_type='变量替换')
            raise exceptions.CustomFuncRunError

        # setup hooks
        setup_hooks = teststep_dict.get("setup_hooks", [])
        setup_hooks.insert(0, "${setup_hook_prepare_kwargs($request)}")
        logger.log_info("-" * 12 + "【请求前置-开始】" + "-" * 12)
        self.step_setup_pass = True
        self.running_hook = 'step_setup'
        self.do_setup_hook_actions(setup_hooks)
        logger.log_info("-" * 12 + "【请求前置-结束】" + "-" * 12)
        if not self.step_setup_pass:
            self.handle_teardown(fail_type='前置动作')
            raise exceptions.SetupHooksFailure

        try:
            url = parsed_request.pop('url')
            method = parsed_request.pop('method')
            group_name = parsed_request.pop("group", None)
        except KeyError:
            raise exceptions.ParamsError("URL or METHOD missed!")

        # TODO: move method validation to json schema
        valid_methods = [
            "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"
        ]
        if method.upper() not in valid_methods:
            err_msg = u"Invalid HTTP method! => {}\n".format(method)
            err_msg += "Available HTTP methods: {}".format(
                "/".join(valid_methods))
            logger.log_error(err_msg)
            self.handle_teardown(fail_type='校验发送方式')
            raise exceptions.ParamsError(err_msg)

        # logger.log_info("{method} {url}".format(method=method, url=url))
        # logger.log_debug("request kwargs(raw): {kwargs}".format(kwargs=parsed_request))

        # request
        try:
            resp = self.http_client_session.request(method,
                                                    url,
                                                    name=group_name,
                                                    **parsed_request)
        except Exception as e:
            self.handle_teardown(fail_type='接口请求')
            raise exceptions.RequestFailure
        resp_obj = response.ResponseObject(resp)

        # # teardown hooks
        # teardown_hooks = teststep_dict.get("teardown_hooks", [])
        # if teardown_hooks:
        #     # logger.log_info("start to run teardown hooks")
        #     logger.log_info("【开始后置动作】...")
        #     self.context.update_teststep_variables_mapping("response", resp_obj)
        #     self.do_hook_actions(teardown_hooks)
        #     logger.log_info("【结束后置动作】")

        # request teardown hooks 新增请求后置
        request_teardown_hooks = teststep_dict.get("request_teardown_hooks",
                                                   [])
        if request_teardown_hooks:
            # logger.log_info("start to run teardown hooks")
            logger.log_info("-" * 12 + "【请求后置-开始】" + "-" * 12)
            self.step_request_teardown_pass = True
            self.running_hook = 'step_request_teardown'
            self.context.update_teststep_variables_mapping(
                "response", resp_obj)
            self.do_request_teardown_hook_actions(request_teardown_hooks)
            logger.log_info("-" * 12 + "【请求后置-结束】" + "-" * 12)
            if not self.step_request_teardown_pass:
                self.handle_teardown(fail_type='请求后置动作')
                raise exceptions.TeardownHooksFailure

        # extract
        logger.log_info("-" * 12 + "【提取变量-开始】" + "-" * 12)
        try:
            extracted_variables_mapping = resp_obj.extract_response(
                extractors, self.context)
            self.context.update_testcase_runtime_variables_mapping(
                extracted_variables_mapping)
        except Exception as err:
            logger.log_error('提取变量失败:{0}'.format(err.args[0]))
            self.handle_teardown(fail_type='提取变量')
            raise exceptions.ExtractFailure
        logger.log_info("-" * 12 + "【提取变量-结束】" + "-" * 12)

        # validate
        try:
            logger.log_info("-" * 12 + "【结果校验-开始】" + "-" * 12)
            self.evaluated_validators, validate_pass = self.context.validate(
                validators, resp_obj)
            logger.log_info("-" * 12 + "【结果校验-结束】" + "-" * 12)
            if not validate_pass:
                # self.handle_teardown(fail_type='结果校验')
                raise exceptions.ValidationFailure
        except (exceptions.ParamsError, exceptions.ValidationFailure,
                exceptions.ExtractFailure, exceptions.VariableNotFound) as err:
            # log request
            # err_req_msg = "request: \n"
            # err_req_msg += "headers: {}\n".format(parsed_request.pop("headers", {}))
            # for k, v in parsed_request.items():
            #     err_req_msg += "{}: {}\n".format(k, repr(v))
            # logger.log_error(err_req_msg)
            #
            # # log response
            # err_resp_msg = "response: \n"
            # err_resp_msg += "status_code: {}\n".format(resp_obj.status_code)
            # err_resp_msg += "headers: {}\n".format(resp_obj.headers)
            # err_resp_msg += "body: {}\n".format(repr(resp_obj.text))
            # logger.log_error(err_resp_msg)
            logger.log_error('结果校验失败')
            self.handle_teardown(fail_type='结果校验')
            raise exceptions.ValidationFailure

        # teardown hooks
        teardown_hooks = teststep_dict.get("teardown_hooks", [])
        self.step_teardown_executed = True
        if teardown_hooks:
            # logger.log_info("start to run teardown hooks")
            logger.log_info("-" * 12 + "【用例后置-开始】" + "-" * 12)
            self.step_teardown_pass = True
            self.running_hook = 'step_teardown'
            self.context.update_teststep_variables_mapping(
                "response", resp_obj)
            self.do_teardown_hook_actions(teardown_hooks)
            logger.log_info("-" * 12 + "【用例后置-结束】" + "-" * 12)
            if not self.step_teardown_pass:
                self.handle_teardown(fail_type='后置动作')
                raise exceptions.TeardownHooksFailure

        # total teardown hooks
        if is_last:
            if self.testcase_teardown_hooks and not self.testcase_teardown_hooks_executed:
                logger.log_info("-" * 12 + "【全局后置-开始】" + "-" * 12)
                self.testcase_teardown_hooks_executed = True
                self.do_teardown_hook_actions(self.testcase_teardown_hooks)
                logger.log_info("-" * 12 + "【全局后置-结束】" + "-" * 12)

        logger.log_info("【结束执行用例】: ID_{0}, {1}".format(case_id, case_name))
Exemplo n.º 8
0
    def _extract_field_with_delimiter(self, field, context_obj=None):
        """ response content could be json or html text.
        @param (str) field should be string joined by delimiter.
        e.g.
            "status_code"
            "headers"
            "cookies"
            "content"
            "headers.content-type"
            "content.person.name.first_name"

            含用例内变量
            "123$phoneNo"

            查询SQL
            "SELECT NEXT_VALUE FROM user_db.sequence WHERE SEQ_NAME='$MEMBER_ID';"
        """
        # [:]
        sub_str_exp = None
        if field.endswith(']') and '[' in field and ':' in field.split(
                '[')[-1]:
            sub_str_exp = '[' + field.split('[')[-1]
            field = field.strip(sub_str_exp)

        # 支持提取变量步骤中写查询sql,查询结果保存为变量
        if str(field).lower().startswith("select "):
            db_connect_content = '$DB_CONNECT'
            parsed_db_connect = context_obj.eval_content(db_connect_content)
            if parser.extract_variables(field):
                sql = context_obj.eval_content(field)
            else:
                sql = field
            from atp.api.mysql_sql_executor import sql_execute, db_operation_to_json
            from atp.utils.tools import convert_mysql_datatype_to_py
            try:
                res = sql_execute(sql, db_connect=parsed_db_connect)
            except Exception as err:
                raise
            if res:
                # 支持查询结果是多条数据的情况
                if len(res) == 1:
                    if len(res[0]) == 1:
                        res_value = convert_mysql_datatype_to_py(res[0][0])
                    else:
                        res_value = db_operation_to_json(
                            sql, db_connect=parsed_db_connect, return_info=res)
                else:
                    res_value = []
                    for res_item in res:
                        if len(res_item) == 1:
                            res_value.append(
                                convert_mysql_datatype_to_py(res_item[0]))
                        else:
                            res_value.append(
                                db_operation_to_json(
                                    sql,
                                    db_connect=parsed_db_connect,
                                    return_info=res_item,
                                    multi=True))
            else:
                res_value = 'variable sql return no result!'
            # res_value = res[0][0] if res else "DB query returns EMPTY result!"
            # if isinstance(res_value, decimal.Decimal):
            #     res_value = float(res_value)
            return res_value, sub_str_exp

        # string.split(sep=None, maxsplit=-1) -> list of strings
        # e.g. "content.person.name" => ["content", "person.name"]
        try:
            top_query, sub_query = field.split('.', 1)
        except ValueError:
            top_query = field
            sub_query = None

        # status_code
        if top_query in ["status_code", "encoding", "ok", "reason", "url"]:
            if sub_query:
                # status_code.XX
                err_msg = u"Failed to extract: {}\n".format(field)
                logger.log_error(err_msg)
                raise exceptions.ParamsError(err_msg)

            return getattr(self, top_query), sub_str_exp

        # cookies
        elif top_query == "cookies":
            cookies = self.cookies.get_dict()
            if not sub_query:
                # extract cookies
                return cookies, sub_str_exp

            try:
                return cookies[sub_query], sub_str_exp
            except KeyError:
                err_msg = u"Failed to extract cookie! => {}\n".format(field)
                err_msg += u"response cookies: {}\n".format(cookies)
                logger.log_error(err_msg)
                raise exceptions.ExtractFailure(err_msg)

        # elapsed
        elif top_query == "elapsed":
            available_attributes = u"available attributes: days, seconds, microseconds, total_seconds"
            if not sub_query:
                err_msg = u"elapsed is datetime.timedelta instance, attribute should also be specified!\n"
                err_msg += available_attributes
                logger.log_error(err_msg)
                raise exceptions.ParamsError(err_msg)
            elif sub_query in ["days", "seconds", "microseconds"]:
                return getattr(self.elapsed, sub_query), sub_str_exp
            elif sub_query == "total_seconds":
                return self.elapsed.total_seconds(), sub_str_exp
            else:
                err_msg = "{} is not valid datetime.timedelta attribute.\n".format(
                    sub_query)
                err_msg += available_attributes
                logger.log_error(err_msg)
                raise exceptions.ParamsError(err_msg)

        # headers
        elif top_query == "headers":
            headers = self.headers
            if not sub_query:
                # extract headers
                return headers, sub_str_exp

            try:
                return headers[sub_query], sub_str_exp
            except KeyError:
                err_msg = u"Failed to extract header! => {}\n".format(field)
                err_msg += u"response headers: {}\n".format(headers)
                logger.log_error(err_msg)
                raise exceptions.ExtractFailure(err_msg)

        # response body
        elif top_query in ["content", "text", "json"]:
            try:
                body = self.json
            except exceptions.JSONDecodeError:
                body = self.text

            if not sub_query:
                # extract response body
                return body, sub_str_exp

            if isinstance(body, dict):
                # content = {"xxx": 123}, content.xxx
                '''如果body中content是字符串类型'content': "{'headImageUrl':'','isRegister':0,'nickName':''}"
                  转换成字典,然后'extract': [{'headImageUrl':"content.content.isRegister"}]可提取
                '''
                # if "content" in body.keys() and '{' in body["content"]:
                # 修复bug:如果body["content"]是NoneType,报错“TypeError:  argument  of  type  'NoneType'  is  not  iterable ”
                # if "content" in body.keys() and body["content"] and '{' in body["content"]:
                #     body_content_dict=json.loads(body["content"].replace("'", "\""))
                #     body["content"]=body_content_dict
                # 修复bug:"[]"未被json.loads
                if "content" in body.keys() and body["content"] and isinstance(
                        body["content"], str):
                    try:
                        body_content_dict = json.loads(body["content"].replace(
                            ' style="text-align: center;text-indent: 0;"',
                            '').replace("'", "\""))
                        body["content"] = body_content_dict
                    except (TypeError, json.decoder.JSONDecodeError) as e:
                        # logger.log_error(body["content"].replace("'", "\""))
                        logger.log_error('\n'.join([e,
                                                    traceback.format_exc()]))
                return utils.query_json(body, sub_query), sub_str_exp
            elif sub_query.isdigit():
                # content = "abcdefg", content.3 => d
                return utils.query_json(body, sub_query), sub_str_exp
            else:
                # content = "<html>abcdefg</html>", content.xxx
                err_msg = u"Failed to extract attribute from response body! => {}\n".format(
                    field)
                err_msg += u"response body: {}\n".format(body)
                logger.log_error(err_msg)
                raise exceptions.ExtractFailure(err_msg)

        # new set response attributes in teardown_hooks
        elif top_query in self.__dict__:
            attributes = self.__dict__[top_query]

            if not sub_query:
                # extract response attributes
                return attributes, sub_str_exp

            if isinstance(attributes, (dict, list)):
                # attributes = {"xxx": 123}, content.xxx
                return utils.query_json(attributes, sub_query), sub_str_exp
            elif sub_query.isdigit():
                # attributes = "abcdefg", attributes.3 => d
                return utils.query_json(attributes, sub_query), sub_str_exp
            else:
                # content = "attributes.new_attribute_not_exist"
                err_msg = u"Failed to extract cumstom set attribute from teardown hooks! => {}\n".format(
                    field)
                err_msg += u"response set attributes: {}\n".format(attributes)
                logger.log_error(err_msg)
                raise exceptions.TeardownHooksFailure(err_msg)

        elif context_obj and parser.extract_variables(top_query):
            # 表达式带已知变量,保存为新的变量
            # ha$phone => ha18551602992
            return context_obj.eval_content(top_query), sub_str_exp

        # others
        else:
            err_msg = u"Failed to extract attribute from response! => {}\n".format(
                field)
            err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, text, json, encoding, ok, reason, url.\n\n"
            err_msg += u"If you want to set attribute in teardown_hooks, take the following example as reference:\n"
            err_msg += u"response.new_attribute = 'new_attribute_value'\n"
            logger.log_error(err_msg)
            raise exceptions.ParamsError(err_msg)
Exemplo n.º 9
0
    def _do_validation(self, validator_dict):
        """ validate with functions

        Args:
            validator_dict (dict): validator dict
                {
                    "check": "status_code",
                    "check_value": 200,
                    "expect": 201,
                    "comparator": "eq"
                }

        """
        # TODO: move comparator uniform to init_test_suites
        comparator = utils.get_uniform_comparator(validator_dict["comparator"])
        validate_func = self.TESTCASE_SHARED_FUNCTIONS_MAPPING.get(comparator)

        if not validate_func:
            raise exceptions.FunctionNotFound(
                "comparator not found: {}".format(comparator))

        check_item = validator_dict["check"]
        check_value = validator_dict["check_value"]
        expect_value = validator_dict["expect"]

        if (check_value is None or expect_value is None) \
            and comparator not in [
                    "is", "eq", "equals", "==", "json_contains", "json_same", "field_special_check", "db_validate",
                    "db_validate_cycle", "field_check_empty_list", "field_check_not_empty_list",
                    "field_check_not_in_list", "field_check_empty_json", "field_check_not_empty_json", "redis_validate", "mq_validate"]:
            raise exceptions.ParamsError(
                "Null value can only be compared with comparator: eq/equals/=="
            )

        # validate_msg = "validate: {} {} {}({})".format(
        validate_msg = "【验证点】: 校验方法:{}, 待校验内容:{}, 期望结果:{}({})".format(
            comparator, check_item, expect_value,
            type(expect_value).__name__)

        try:
            validator_dict["check_result"] = "pass"
            is_ok = validate_func(check_value, expect_value)
            if is_ok is True:
                validate_msg += "\t ....................PASS"
                logger.log_info(validate_msg)
            else:
                validate_msg += "\t ....................FAIL"
                if is_ok is not False:
                    validate_msg += "......原因: {}".format(is_ok[1])
                logger.log_error(validate_msg)
                validator_dict["check_result"] = "fail"
                raise exceptions.ValidationFailure(validate_msg)
        except (AssertionError, TypeError) as err:
            validate_msg += "\t ....................FAIL"
            validate_msg += "\t{}({}), {}, {}({})".format(
                check_value,
                type(check_value).__name__, comparator, expect_value,
                type(expect_value).__name__)
            validate_msg += "\t......原因: {}".format(err.args[0])
            logger.log_error(validate_msg)
            validator_dict["check_result"] = "fail"
            raise exceptions.ValidationFailure(validate_msg)