Пример #1
0
def render_html_report(summary,
                       html_report_name=None,
                       html_report_template=None,
                       html_report_dir=None):
    """ render html report with specified report name and template
        if html_report_name is not specified, use current datetime
        if html_report_template is not specified, use default report template
    """
    if not html_report_template:
        html_report_template = os.path.join(
            os.path.abspath(os.path.dirname(__file__)), "templates",
            "report_template.html")
        logger.log_debug("No html report template specified, use default.")
    else:
        # logger.log_info("【使用Html报告模板】: {}".format(html_report_template))
        pass

    logger.log_info("【开始生成Html报告】 ...")
    # logger.log_debug("【渲染报告数据】: {}".format(summary))

    if html_report_dir:
        report_dir_path = html_report_dir
    else:
        report_dir_path = os.path.join(loader.project_working_directory,
                                       "reports")
    start_at_timestamp = int(summary["time"]["start_at"])
    summary["time"]["start_datetime"] = datetime.fromtimestamp(
        start_at_timestamp).strftime('%Y-%m-%d %H:%M:%S')
    if html_report_name:
        summary["html_report_name"] = html_report_name
        report_dir_path = os.path.join(report_dir_path, html_report_name)
        html_report_name += "-{}.html".format(start_at_timestamp)
    else:
        summary["html_report_name"] = ""
        html_report_name = "{}.html".format(start_at_timestamp)

    if not os.path.isdir(report_dir_path):
        os.makedirs(report_dir_path)

    for index, suite_summary in enumerate(summary["details"]):
        if not suite_summary.get("name"):
            suite_summary["name"] = "test suite {}".format(index)
        for record in suite_summary.get("records"):
            meta_data = record['meta_data']
            stringify_data(meta_data, 'request')
            stringify_data(meta_data, 'response')

    with io.open(html_report_template, "r", encoding='utf-8') as fp_r:
        template_content = fp_r.read()
        report_path = os.path.join(report_dir_path, html_report_name)
        with io.open(report_path, 'w', encoding='utf-8') as fp_w:
            rendered_content = Template(template_content,
                                        extensions=["jinja2.ext.loopcontrols"
                                                    ]).render(summary)
            fp_w.write(rendered_content)

    logger.log_info("【已生成Html报告】: {}".format(report_path))

    return report_path
Пример #2
0
 def do_setup_hook_actions(self, actions):
     for action in actions:
         # logger.log_debug("call hook: {}".format(action))
         logger.log_info("【执行前置动作】: {}".format(action))
         # TODO: check hook function if valid
         self.context.eval_content(action, runner=self)
         if not self.step_setup_pass:
             break
Пример #3
0
def parse_string_variables(content, variables_mapping, runner=None):
    """ parse string content with variables mapping.

    Args:
        runner:
        content (str): string content to be parsed.
        variables_mapping (dict): variables mapping.

    Returns:
        str: parsed string content.

    Examples:
        >>> content = "/api/users/$uid"
        >>> variables_mapping = {"$uid": 1000}
        >>> parse_string_variables(content, variables_mapping)
            "/api/users/1000"

    """
    variables_list = extract_variables(content)
    for variable_name in variables_list:
        variable_not_found = False
        try:
            variable_value = get_mapping_variable(variable_name,
                                                  variables_mapping)
        except (KeyError, exceptions.VariableNotFound) as err:
            # logger.log_debug("\n".join([str(err), traceback.format_exc()]))
            variable_not_found = True
            runner.variable_not_found = True

        if variable_not_found:
            break

        # TODO: replace variable label from $var to {{var}}
        if "${}".format(variable_name) == content:
            # content is a variable

            logger.log_info("【参数替换】: {content} 替换为==> {value}".format(
                content=content, value=variable_value))
            content = variable_value
        else:
            # content contains one or several variables
            if not isinstance(variable_value, str):
                variable_value = builtin_str(variable_value)

            logger.log_info("【参数替换】: ${name} 替换为==> {value}".format(
                name=variable_name, value=variable_value))
            content = content.replace("${}".format(variable_name),
                                      variable_value, 1)

    return content
Пример #4
0
def sql_execute_with_params(sql_list,
                            param_list,
                            env_name=None,
                            db_connect=None):
    """
    执行数据库操作,支持携带参数
    """
    if len(sql_list) != len(param_list):
        raise Exception("sql个数同参数个数不匹配")
    if env_name:
        try:
            obj = EnvInfo.query.filter_by(env_name=env_name).first()
            if not obj:
                raise Exception("传入的环境不存在")
            db_info = obj.db_connect
            # db_info = db_connects[env][db_type]

        except Exception as err:
            raise Exception(err)
    elif db_connect:
        db_info = db_connect

    engine = create_engine(db_info, echo=False, poolclass=NullPool)
    return_info = None
    with engine.connect() as conn:
        try:
            for s, p in zip(sql_list, param_list):
                if 'insert' not in s.lower() and 'where' not in s.lower():
                    raise Exception('更新和删除操作不符合安全规范,必须要带上where条件')
                if s.lower().strip().startswith('select'):
                    return_info = conn.execute(text(s), p).fetchall()
                    hr_logger.log_info("【执行SQL】: {0}  返回数据: {1}".format(
                        s.replace('\n', ''), return_info))
                else:
                    return_info = conn.execute(text(s), p).rowcount
                    # logger.info("受影响的行: {0}".format(return_info))
                    hr_logger.log_info("【执行SQL】: {0}  受影响的行: {1}".format(
                        s.replace('\n', ''), return_info))
        except exc.SQLAlchemyError as err:
            hr_logger.log_error("数据库操作失败, {0}".format(err))
            raise err

        finally:
            conn.close()

    return return_info
Пример #5
0
def sql_execute(sql, env_name=None, db_connect=None):
    """
    """
    # env = env.upper()
    if env_name:
        try:
            obj = EnvInfo.query.filter_by(env_name=env_name).first()
            if not obj:
                raise Exception("传入的环境不存在")
            db_info = obj.db_connect
            # db_info = db_connects[env][db_type]

        except Exception as err:
            raise Exception(err)
    elif db_connect:
        db_info = db_connect

    engine = create_engine(db_info, echo=False, poolclass=NullPool)
    return_info = None
    with engine.connect() as conn:
        try:
            sql = sql.replace('%', '%%')
            if re.match('select', sql.lower().strip()):
                return_info = conn.execute(sql).fetchall()
            elif re.match('exec', sql.lower().strip()):
                sql_new = DDL(sql)
                conn.execute(sql_new)
            else:
                for s in str(sql).strip().strip(';').split(';'):
                    if 'insert' not in s.lower() and 'where' not in s.lower():
                        raise Exception('更新和删除操作不符合安全规范,必须要带上where条件')
                    return_info = conn.execute(s).rowcount
                    # logger.info("受影响的行: {0}".format(return_info))
                    hr_logger.log_info("【执行SQL】: {0}  受影响的行: {1}".format(
                        s.replace('\n', ''), return_info))
        except exc.SQLAlchemyError as err:
            hr_logger.log_error("数据库操作失败, {0}".format(err))
            raise err
            # hr_logger.log_error(err.args[0])

        finally:
            conn.close()

    return return_info
Пример #6
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
Пример #7
0
def db_operation_to_json_cycle(sql, db_connect, expect_value, wait_time=30):
    """数据库json校验 轮询"""
    step_time = 5
    t = 0
    return_value = None

    dict_expect = transfer_json_string_to_dict(expect_value)
    # 如果期望值非json格式,直接报错
    if not isinstance(dict_expect, dict):
        raise Exception('结果校验中期望结果非json格式,期望结果内容为{0}({1})'.format(
            dict_expect,
            type(dict_expect).__name__))
    dict_expect_lower_key = {
        key.lower(): dict_expect[key]
        for key in dict_expect
    }

    while t <= wait_time:
        try:
            return_value = db_operation_to_json(sql, db_connect)
            dict_check = transfer_json_string_to_dict(return_value)
            dict_check_lower_key = {
                key.lower(): dict_check[key]
                for key in dict_check
            }

            if not dict_check_lower_key:
                hr_logger.log_info("【轮询SQL】: {0}  表中无数据,等待5秒后重试".format(
                    sql.replace('\n', '')))
            else:
                res = is_json_contains(dict_check_lower_key,
                                       dict_expect_lower_key)
                if res is True:
                    hr_logger.log_info('【轮询SQL】: {0}  结果为 {1}'.format(
                        sql.replace('\n', ''), dict_check))
                    return return_value
                else:
                    hr_logger.log_info('【轮询SQL】: {0}  结果为 {1},等待5秒后重试'.format(
                        sql.replace('\n', ''), dict_check))

            time.sleep(step_time)
            t += step_time

        except Exception as err:
            logger.error(traceback.format_exc())
            raise Exception("【轮询SQL】: 数据库操作失败, {0}".format(err))

    hr_logger.log_info('【轮询SQL】: 超过{0}秒表中仍无预期数据'.format(wait_time))

    return return_value
Пример #8
0
def load_dot_env_file():
    """ load .env file, .env file should be located in project working directory.

    Returns:
        dict: environment variables mapping

            {
                "UserName": "******",
                "Password": "******",
                "PROJECT_KEY": "ABCDEFGH"
            }

    Raises:
        exceptions.FileFormatError: If env file format is invalid.

    """
    path = os.path.join(project_working_directory, ".env")
    if not os.path.isfile(path):
        # logger.log_debug(".env file not exist in : {}".format(project_working_directory))
        # logger.log_debug(" 未使用.env文件".format())
        return {}

    logger.log_info("Loading environment variables from {}".format(path))
    env_variables_mapping = {}
    with io.open(path, 'r', encoding='utf-8') as fp:
        for line in fp:
            if "=" in line:
                variable, value = line.split("=")
            elif ":" in line:
                variable, value = line.split(":")
            else:
                raise exceptions.FileFormatError(".env format error")

            env_variables_mapping[variable.strip()] = value.strip()

    project_mapping["env"] = env_variables_mapping
    utils.set_os_environ(env_variables_mapping)

    return env_variables_mapping
Пример #9
0
def mq_validate(check_value, expect_value):
    """ mq-消息内容包含校验 : topic+tag+system_name, 预期结果
    :desc: 说明: 根据topic+tag+system_name查询消息,同预期结果匹配
    :param check_value:待校验内容: 需要查询的消息topic和tag,以json格式,如{"topic": "TP_MIME_UNION_FINANCE", "tag": "TAG_capital-mgmt-core_createCashLoan", "system_name": "user-core"},支持使用变量
    :param expect_value:期望值: 例如 "abc" 或 {"a": 123, "b": "xxx"},支持使用变量
    :return:
    """
    # 返回ture和flase,关联测试报告success和fail
    if not check_value:
        return False, '根据topic和tag获取不到最近10分钟的MQ消息'

    # hr_logger.log_info(str(len(check_value)))
    for index, item in enumerate(check_value):
        hr_logger.log_info('消息({0})内容:{1}'.format(str(index+1), item))
        try:
            dict_check = json.loads(item)
            # hr_logger.log_info(type(dict_check).__name__)
            if not isinstance(expect_value, dict):
                return False, '预期结果和实际结果类型不一致,预期结果是"{0}",期望结果是"{1}"'.format(type(dict_check).__name__, type(expect_value).__name__)

            is_ok = is_json_contains(dict_check, expect_value)
            # hr_logger.log_info(type(is_ok).__name__)
            if is_ok is True:
                return True
            else:
                continue

        except Exception as err:
            hr_logger.log_error(traceback.format_exc())
            dict_check = item

        if dict_check == expect_value:
            return True
        elif str(dict_check) == str(expect_value):
            return True
        else:
            continue

    return False, '找不到同预期结果匹配的MQ消息'
Пример #10
0
    def handle_teardown(self, fail_type):
        logger.log_warning("因【{}】错误, 中断测试".format(fail_type))
        if not self.step_teardown_executed:
            teardown_hooks = self.teststep_dict.get("teardown_hooks", [])
            self.step_teardown_executed = True
            if teardown_hooks:
                logger.log_info("-" * 12 + "【用例后置-开始】" + "-" * 12)
                self.do_teardown_hook_actions(teardown_hooks)
                logger.log_info("-" * 12 + "【用例后置-结束】" + "-" * 12)

        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)
Пример #11
0
Файл: api.py Проект: zmhtest/ATP
    def run(self, path_or_testcases, mapping=None):
        """ start to run test with variables mapping.

        Args:
            path_or_testcases (str/list/dict): YAML/JSON testcase file path or testcase list
                path: path could be in several type
                    - absolute/relative file path
                    - absolute/relative folder path
                    - list/set container with file(s) and/or folder(s)
                testcases: testcase dict or list of testcases
                    - (dict) testset_dict
                    - (list) list of testset_dict
                        [
                            testset_dict_1,
                            testset_dict_2
                        ]
            mapping (dict): if mapping specified, it will override variables in config block.

        Returns:
            instance: HttpRunner() instance

        """
        # loader
        testcases_list = self.load_tests(path_or_testcases)
        # parser
        parsed_testcases_list = self.parse_tests(testcases_list)

        # initialize
        unittest_runner, test_suite = self.__initialize(parsed_testcases_list)

        # aggregate
        self.summary = {
            "success": True,
            "stat": {},
            "time": {},
            "platform": report.get_platform(),
            "details": []
        }

        # execution
        for testcase in test_suite:
            testcase_name = testcase.config.get("name")
            testcase_id = testcase.config.get("case_id")
            chain_list = testcase.config.get("chain_list")
            is_main = testcase.config.get("is_main")
            logger.log_info("【准备运行测试用例】:")
            logger.log_info("【用例ID】: {}".format(testcase_id))
            logger.log_info("【用例名称】: {}".format(testcase_name))
            if len(chain_list) > 1:
                if is_main:
                    chain = ' -> '.join(str(i) for i in chain_list)
                    logger.log_info("【根据全链路用例组成调用链(ID)】: {}".format(chain))
                else:
                    chain = ' -> '.join(str(i) for i in chain_list)
                    logger.log_info("【存在前置用例】")
                    logger.log_info("【根据前置用例组成调用链(ID)】: {}".format(chain))

            result = unittest_runner.run(testcase)
            testcase_summary = report.get_summary(result)

            # summary增加case_id信息
            testcase_summary["is_main"] = is_main
            testcase_summary["case_id"] = testcase_id
            testcase_summary["chain_list"] = chain_list
            self.summary["success"] &= testcase_summary["success"]
            testcase_summary["name"] = testcase_name
            testcase_summary["base_url"] = testcase.config.get("request", {}).get("base_url", "")

            # in_out = utils.get_testcase_io(testcase)
            # utils.print_io(in_out)
            testcase_summary["in_out"] = {"in": {}, "out": {}}

            report.aggregate_stat(self.summary["stat"], testcase_summary["stat"])
            report.aggregate_stat(self.summary["time"], testcase_summary["time"])

            self.summary["details"].append(testcase_summary)

        return self
Пример #12
0
def parse_string_functions(content,
                           variables_mapping,
                           functions_mapping,
                           runner=None):
    """ parse string content with functions mapping.

    Args:
        content (str): string content to be parsed.
        variables_mapping (dict): variables mapping.
        functions_mapping (dict): functions mapping.
        runner (object)

    Returns:
        str: parsed string content.

    Examples:
        >>> content = "abc${add_one(3)}def"
        >>> functions_mapping = {"add_one": lambda x: x + 1}
        >>> parse_string_functions(content, functions_mapping)
            "abc4def"

    """
    functions_list = extract_functions(content)
    for func_content in functions_list:
        # logger.log_debug("【执行函数】: {}".format(func_content))
        function_meta = parse_function(func_content)
        func_name = function_meta["func_name"]
        logger.log_info("【识别函数】: {}".format(func_name))

        args = function_meta.get("args", [])
        kwargs = function_meta.get("kwargs", {})
        args = parse_data(args, variables_mapping, functions_mapping)
        logger.log_info("【函数{0}参数列表】: {1}".format(func_name, args))
        kwargs = parse_data(kwargs, variables_mapping, functions_mapping)

        if func_name in ["parameterize", "P"]:
            from httprunner import loader
            eval_value = loader.load_csv_file(*args, **kwargs)
        else:
            func = get_mapping_function(func_name, functions_mapping)
            try:
                eval_value = func(*args, **kwargs)
                if eval_value is False:
                    logger.log_error("【函数{0}异常返回】: {1}".format(
                        func_name, eval_value))
                else:
                    logger.log_info("【函数{0}返回】: {1}".format(
                        func_name, eval_value))
            except Exception as err:
                logger.log_error("【函数{0}异常返回】: {1}".format(
                    func_name, err.args[0]))
                eval_value = '自定义方法调用失败'

            if eval_value is False or eval_value == '自定义方法调用失败':
                if runner.running_hook == 'step_setup':
                    runner.step_setup_pass = False
                elif runner.running_hook == 'step_teardown':
                    runner.step_teardown_pass = False
                elif runner.running_hook == 'step_request_teardown':
                    runner.step_request_teardown_pass = False
                elif runner.running_hook == 'step_parse_variable':
                    runner.step_parse_variable_pass = False

        func_content = "${" + func_content + "}"
        if func_content == content:
            # content is a function, e.g. "${add_one(3)}"
            content = eval_value
        else:
            # content contains one or many functions, e.g. "abc${add_one(3)}def"
            content = content.replace(func_content, str(eval_value), 1)

    return content
Пример #13
0
def http_runner_run(**kwargs):
    """调用HttpRunner运行测试"""
    log_dir = kwargs.pop('log_dir')
    env_id = kwargs.pop('env_id')
    testset = kwargs.pop('testset')
    test_meta_list = kwargs.pop('test_meta_list')
    run_task_result_id = kwargs.pop('run_task_result_id')
    intf_id = kwargs.pop('intf_id', None)
    main_case_id = kwargs.pop('main_case_id', None)
    main_case_id_list = kwargs.pop('main_case_id_list', None)

    if intf_id:
        log_path = '{0}task_run_{1}_intf_{2}.log'.format(log_dir, run_task_result_id, intf_id)
    elif main_case_id:
        log_path = '{0}task_run_{1}_main_case_{2}.log'.format(log_dir, run_task_result_id, main_case_id)
    else:
        log_path = '{0}task_run_{1}_main_case_list_{2}.log'.format(log_dir, run_task_result_id, main_case_id_list)

    # 初始化hr_runner
    hr_kwargs = {
        "failfast": True,
        "log_path": log_path
    }
    hr_runner = HttpRunner(**hr_kwargs)

    start_time = time.strftime('%Y-%m-%d %H-%M-%S', time.localtime(time.time()))
    hr_logger.log_warning("【START】测试开始! (ง •_•)ง")
    hr_logger.log_warning("【环境】: {}".format(env_id))
    # time.sleep(3)
    try:
        testset_json = json_dumps(testset)
    except Exception:
        testset_json = testset

    # 执行测试
    try:
        # hr_logger.log_warning("【调用HttpRunner】: {0}".format(testset_json))
        hr_runner.run(testset)
        hr_logger.log_info("【结束调用HttpRunner】")
    except Exception:
        raise Exception(traceback.format_exc())

    for detail in hr_runner.summary["details"]:
        for record in detail["records"]:
            record["meta_data"]["request"].pop("files", None)

    # 去除summary中的文件对象
    summary_remove_file_obj(hr_runner.summary)

    # 完善summary
    summary = deepcopy(hr_runner.summary)
    # summary = hr_runner.summary
    perfect_summary(summary, test_meta_list)

    # print(json_dumps(summary))
    summary = add_memo(summary)

    # 识别错误
    # print(json_dumps(summary))
    summary = identify_errors(summary)

    return {"summary": json_loads(json_dumps(summary)), "run_task_result_id": run_task_result_id, 'log_dir': log_dir}
Пример #14
0
def sql_execute_cycle(sql, db_connect, expect_value, wait_time=30):
    """
    定时执行某条查询sql,用于特定场景,如检查贷款表中贷款数据是否生成,默认每5秒检查一次,1分钟超时退出  \n
    :param expect_value:
    :param db_connect:
    :param sql: 待执行的查询sql  \n
    :param wait_time: 超时时长,超过该时间自动退出 ,默认60秒 \n
    :return: 返回查询结果  \n
    """
    expect_is_json = False
    step_time = 5
    t = 0
    return_value = None
    dict_expect = transfer_json_string_to_dict(expect_value)
    if isinstance(dict_expect, dict):
        expect_is_json = True

    while t <= wait_time:
        try:
            return_info = sql_execute(sql, db_connect=db_connect)
            if expect_is_json:
                return_value = return_info[0][0] if return_info else None
                dict_check = transfer_json_string_to_dict(return_value)
                # dict_check_lower_key = {key.lower(): dict_check[key] for key in dict_check}
                if not dict_check:
                    hr_logger.log_info("【轮询SQL】: {0}  表中无数据,等待5秒后重试".format(
                        sql.replace('\n', '')))
                else:
                    res = is_json_contains(dict_check, dict_expect)
                    if res is True:
                        hr_logger.log_info('【轮询SQL】: {0}  结果为 {1}'.format(
                            sql.replace('\n', ''), dict_check))
                        return return_value
                    else:
                        hr_logger.log_info(
                            '【轮询SQL】: {0}  结果为 {1},{2},等待5秒后重试'.format(
                                sql.replace('\n', ''), dict_check, res[1]))
            else:
                if not return_info:
                    hr_logger.log_info("【轮询SQL】: {0}  表中无数据,等待5秒后重试".format(
                        sql.replace('\n', '')))
                else:
                    return_value = str(return_info[0][0])
                    if return_value == str(expect_value):
                        hr_logger.log_info('【轮询SQL】: {0}  结果为 {1}'.format(
                            sql.replace('\n', ''), return_value))
                        return return_info[0][0]
                    else:
                        hr_logger.log_info(
                            '【轮询SQL】: {0}  结果为 {1},等待5秒后重试'.format(
                                sql.replace('\n', ''), return_value))

            time.sleep(step_time)
            t += step_time

        except Exception as err:
            logger.error(traceback.format_exc())
            raise Exception("【轮询SQL】: 数据库操作失败, {0}".format(err))

    hr_logger.log_info('【轮询SQL】: 超过{0}秒表中仍无预期数据'.format(wait_time))
    return return_value
Пример #15
0
 def do_request_teardown_hook_actions(self, actions):
     for action in actions:
         # logger.log_debug("call hook: {}".format(action))
         logger.log_info("【执行请求后置动作】: {}".format(action))
         # TODO: check hook function if valid
         self.context.eval_content(action, runner=self)
Пример #16
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))
Пример #17
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)
Пример #18
0
def api_run_test(**kwargs):
    report_id = kwargs.pop('report_id', None)
    plan_name = kwargs.pop('plan_name', None)
    project_id = kwargs.pop('project_id', None)
    testcase_main_id_list = kwargs.get('testcase_main_id_list', None)
    failfast = kwargs.pop('failfast', False)
    if testcase_main_id_list:
        is_main = True
    else:
        is_main = False

    try:
        logger.debug(
            '=============================={dir}run_{report_id}.log'.format(
                dir=run_case_log_dir, report_id=report_id))
        hr_kwargs = {
            "failfast":
            failfast,
            "log_path":
            '{dir}run_{report_id}.log'.format(dir=run_case_log_dir,
                                              report_id=report_id)
        }
        runner = HttpRunner(**hr_kwargs)

        # res = load_test(**kwargs)
        # testset = res[0]
        # test_meta_list = res[1]
        # project_id = res[2]

        loader = ApiTestLoader(**kwargs)
        testset = loader.get_testset_list()
        test_meta_list = loader.get_test_meta_list()

        if not testset:
            raise LoadCaseError('没有可执行的用例')

        logger.debug("{1} testset:{0}".format(testset, type(testset)))

    except Exception as err:
        save_report(report_path=None,
                    runner_summary=None,
                    project_id=project_id,
                    report_id=report_id)
        hr_logger.log_error("【ERROR】组装用例出错!")
        hr_logger.log_error('\n'.join([str(err), traceback.format_exc()]))
        hr_logger.log_info("【END】测试结束!")
        hr_logger.remove_handler(runner.handler)
        raise LoadCaseError

    try:
        # summary = run(testset, report_name='testMock')

        start_time = time.strftime('%Y-%m-%d %H-%M-%S',
                                   time.localtime(time.time()))
        hr_logger.log_info("【START】测试开始! (ง •_•)ง")
        hr_logger.log_info("【环境】: {}".format(kwargs.get('env_name', None)))
        # time.sleep(3)
        try:
            testset_json = json_dumps(testset)
        except Exception:
            testset_json = testset
        hr_logger.log_debug("【调用HttpRunner】: {0}".format(testset_json))
        runner.run(testset)
        hr_logger.log_info("【结束调用HttpRunner】")
        # raise RunCaseError
        perfect_summary(runner.summary, test_meta_list)
        """记录用例复用记录"""
        summary_remove_file_obj(runner.summary)
        summary_for_reuse = copy.deepcopy(runner.summary)
        summary_for_reuse = add_memo(summary_for_reuse)
        # 识别错误
        summary_for_reuse = identify_errors(summary_for_reuse)
        # 更新api_testcase_reuse_record表, 并获取covered_intf_id_set, run_cases, success_cases
        save_testcase_reuse_record([{
            "summary":
            json_loads(json_dumps(summary_for_reuse))
        }])
        del summary_for_reuse

        # hr_logger.log_info("【runner.summary】: {}".format(runner.summary))
        '''报告优化:1、汉化(包括日志里面的字段)
                    2、开始时间和持续时间合并成一行
                    3、增加一个字段“错误类型”,如果用例错误,显示该字段,并说明期望与预期值;
                                                否则该字段不显示
                    4.log里面去掉一些数据重复和不重要的;行和字段(请求headers,返回体的headers,reason,url,“”ok”)
                    5.将请求体和返回值数据缩进,且字典里面的key颜色加粗
                    6.新增接口请求类型字段,http、dubbo、mq'''

        for detail in runner.summary["details"]:
            for record in detail["records"]:
                '''增加用例类型:test_meta_list["intf_type"]'''
                record["intf_type"] = test_meta_list[0]["intf_type"]
                '''删除报告一些无需关注的字段'''
                request_keys = ["json", "start_timestamp"]
                response_keys = [
                    "elapsed_ms", "encoding", 'ok', 'url', 'reason', 'cookies'
                ]
                for request_key in request_keys:
                    if request_key in record["meta_data"]["request"]:
                        del record["meta_data"]["request"][request_key]
                for respones_key in response_keys:
                    if respones_key in record["meta_data"]["response"]:
                        del record["meta_data"]["response"][respones_key]
                '''record.status出现error, 抛出错误信息'''
                if record['status'] == 'error':
                    error_msg = record['attachment']
                    raise Exception(error_msg)

                # '''将body和content字节类型转换dic'''
                # if "body" in record["meta_data"]["request"].keys() and "content" in record["meta_data"]["response"].keys():
                #     request_body = record["meta_data"]["request"].pop("body")
                #     response_content = record["meta_data"]["response"].pop("content")
                #     if not request_body:
                #         request_body_dic = {}
                #     else:
                #         try:
                #             request_body_dic = json.loads(request_body)
                #         except TypeError:
                #             request_body_dic = json.loads(request_body.decode('utf-8'))
                #         # 增加捕获异常
                #         except UnicodeDecodeError:
                #             if isinstance(request_body, bytes):
                #                 request_body_dic = {}
                #                 # request_body_dic = request_body.decode('utf-8', 'ignore')
                #             else:
                #                 request_body_dic = {}
                #
                #     if not response_content:
                #         response_content_dic = {}
                #     else:
                #         try:
                #             response_content_dic = json.loads(response_content)
                #         except TypeError:
                #             response_content_dic = json.loads(response_content.decode('utf-8'))
                #         except json.decoder.JSONDecodeError:
                #             response_content_dic = {}
                #
                #     record["meta_data"]["request"]["body"] = request_body_dic
                #     record["meta_data"]["response"]["content"] = response_content_dic
                #
                # '''将files去除,避免报告超长影响展示效果'''
                # if "files" in record["meta_data"]["request"].keys():
                #     record["meta_data"]["request"].pop("files")
                '''报告增加一列:错误类型:'''
                for validate in record["meta_data"]["validators"]:
                    if validate["comparator"] == "json_contains":
                        check_value = validate["check_value"]
                        expect_value = validate["expect"]
                        if json_contains(check_value,
                                         expect_value) is not True:
                            validate["check_result"] = "fail"
                            record["status"] = "failure"
                            detail["stat"]["failures"] += 1
                            detail["stat"]["successes"] -= 1
                            runner.summary["stat"]["failures"] += 1
                            runner.summary["stat"]["successes"] -= 1
                            error_log = ("预期:{}未在返回报文内".format(expect_value))
                            validate["error_log"] = {
                                "json_contains": error_log
                            }
                    elif validate["comparator"] == "db_validate":
                        check_value = validate["check_value"]
                        expect_value = validate["expect"]
                        if db_validate(check_value, expect_value) is not True:
                            validate["check_result"] = "fail"
                            record["status"] = "failure"
                            detail["stat"]["failures"] += 1
                            detail["stat"]["successes"] -= 1
                            runner.summary["stat"]["failures"] += 1
                            runner.summary["stat"]["successes"] -= 1
                            error_log = ("预期:{0},实际是:{1}".format(
                                expect_value, check_value))
                            validate["error_log"] = {"db_validate": error_log}
                    elif validate["comparator"] == "db_json_validate":
                        check_value = validate["check_value"]
                        expect_value = validate["expect"]
                        if not db_json_validate(check_value, expect_value):
                            validate["check_result"] = "fail"
                            record["status"] = "failure"
                            detail["stat"]["failures"] += 1
                            detail["stat"]["successes"] -= 1
                            runner.summary["stat"]["failures"] += 1
                            runner.summary["stat"]["successes"] -= 1
                            error_log = ("预期:{0},实际是:{1}".format(
                                expect_value,
                                json.dumps(check_value).encode('utf-8').decode(
                                    'unicode_escape')))
                            validate["error_log"] = {
                                "db_json_validate": error_log
                            }

        hr_logger.log_info("【runner.summary】: {}".format(
            json_dumps(runner.summary)))
        runner_summary = copy.deepcopy(runner.summary)
        """把每条用例执行成功与否记录到testcase_info.last_run"""
        try:
            save_last_run(runner_summary, is_main=is_main)
        except Exception as e:
            logger.error('\n'.join([str(e), traceback.format_exc()]))
            # hr_logger.log_error("【ERROR】运行用例出错!")
            # hr_logger.log_error('\n'.join([str(e), traceback.format_exc()]))

        # logger.debug("runner_summary_list{}".format(runner.summary))
        # report_path = runner.gen_html_report(
        #     html_report_name=plan_name if plan_name else 'default',
        #     html_report_template=config.REPORT_TEMPLATE_PATH,
        #     html_report_dir=config.REPORT_DIR
        # )
        # logger.debug('report_path:{}'.format(report_path))
        # report_path = report_path.split('reports')[1]
        # report_url = get_host() + r':8899/reports' + report_path
        #
        # logger.debug('AC report_path:{}'.format(report_path))
        report_url = '不生成报告'
        save_report(report_url,
                    runner_summary,
                    project_id,
                    report_id=report_id,
                    is_main=is_main)

    except Exception as err:
        save_report(report_path=None,
                    runner_summary=runner.summary,
                    project_id=project_id,
                    report_id=report_id)
        hr_logger.log_error("【ERROR】运行用例出错!")
        hr_logger.log_error('\n'.join([str(err), traceback.format_exc()]))
        raise RunCaseError
    finally:
        hr_logger.log_info("【END】测试结束!")
        hr_logger.remove_handler(runner.handler)

    return report_url
Пример #19
0
def get_access_token(phone, base_host, redis_connect, db_connect):
    """ 登录-手机号动码登录获取accessToken : 手机号
    :desc: 说明: 指定手机号,依次调用获取验证码和动码登录接口,返回accessToken
    :param phone:手机号: 例如 18555555555
    :return:
    """
    # 根据手机号获取memberid
    # 根据memberid获取token
    try:
        _redis_connect = json.loads(redis_connect)
    except Exception as err:
        raise Exception('环境信息中redis连接配置错误')
    redis_host = _redis_connect['host']
    redis_port = _redis_connect['port']
    redis_password = _redis_connect['password']


    get_member_id_sql = "" % phone
    ret = sql_execute(get_member_id_sql, db_connect=db_connect)
    if ret and ret[0][0]:
        member_id = ret[0][0]
        with RedisUtils(redis_host, redis_port, redis_password, 2) as r:
            user_info = r.get_str_value('user_token_%s' % (str(member_id)))
        if isinstance(user_info, dict):
            return user_info['accessToken']

    hr_logger.log_info('【执行函数{0}】Redis中获取不到手机号【{1}】的accessToken数据,将调用动码登录接口获取'
                       .format(inspect.stack()[0][3], phone))
    current_timestamp = int((time.time() * 1000))
    heard_info = {'Content-Type': 'application/json;charset=utf-8'}
    send_code_url = base_host + '/user/v2/common/sendMobileCode'
    send_code_input = {
        "appld":  "6916984",
        "phone":  str(phone),
        "sourceFlag":  "1",
        "timestamp":  current_timestamp,
    }

    # 加签
    send_code_input = get_sign_common(send_code_input)

    # 获取验证码
    hc = HttpClient()
    try:
        response = hc.http_post(send_code_url, heard_info, send_code_input)
    except Exception as err:
        raise err
    res_dic = json.loads(response.text)
    hr_logger.log_info('【执行函数{0}】获取验证码接口返回内容{1}'.format(inspect.stack()[0][3], response.text))

    if 'content' not in res_dic:
        raise Exception('【执行函数{0}】调用获取验证码接口没有返回content字段,接口返回内容{1}'
                        .format(inspect.stack()[0][3], response.text))
    if 'serialNo' not in res_dic['content']:
        raise Exception('【执行函数{0}】调用获取验证码接口没有返回serialNo字段,接口返回内容{1}'
                        .format(inspect.stack()[0][3], response.text))
    serial_no = eval(res_dic['content'])['serialNo']

    login_url = base_host + '/user/login/v3/phoneLogin'
    login_input = {
        "phone":  str(phone),
        "smsSerialNo":  serial_no,
        "timestamp":  current_timestamp,
        "verifyCode":  "8888",
    }
    # 加签
    login_input = get_sign_common(login_input)

    # 动码登录
    try:
        response = hc.http_post(login_url, heard_info, login_input)
    except Exception as err:
        raise err

    res_dic = json.loads(response.text)
    hr_logger.log_info('【执行函数{0}】动码登录接口返回内容{1}'.format(inspect.stack()[0][3], response.text))

    if 'accessToken' not in res_dic:
        raise Exception('调用登录接口没有返回accessToken字段,接口返回内容{0}'.format(response.text))
    access_token = res_dic['accessToken']

    if not access_token:
        raise Exception('动码登录失败,具体原因请参考上面日志中动码登录接口返回内容')

    return access_token