httprunner学习实践(五)-- skip机制和hook机制

序言

httprunner的使用和其中的一些概念在官方的文档中已经详细说明,写这个学习记录是为了记录下自己的学习历程,能够在今后快速的深入学习和实践。没有提及的部分,请查看官方文档
版本:2.2.5

参考

HttpRunner V2.x 中文使用文档

skip机制

skip类型

  • skip: 无条件跳过测试
  • skipIf: 判断结果返回为true时,跳过测试
  • skipUnless:判断结果返回为false时,跳过测试

使用方法

api中增加skip节点或在testcaseteststeps增加skip节点。

  • skip跳过测试,值为跳过测试的说明
-
    api: api/sale/order_products.yml
    skip: 无条件跳过
  • skipIf跳过测试,值为验证方法,方法在debugtalk.py中定义
    debugtalk.py
def varIsNormal(var):
    if var:
        return False
    return True

testcase

-
    api: api/sale/order_list.yml
    skipIf: ${varIsNormal($token)}
  • skipUnless跳过测试,值为验证方法,方法在debugtalk.py中定义
-
    api: api/sale/order_detail.yml
    skipUnless: ${varIsNormal($token)}

运行机制,如果判断test中有skipskipIfskipUnless,抛出SkipTest异常。

    def _handle_skip_feature(self, test_dict):
        """ handle skip feature for test
            - skip: skip current test unconditionally
            - skipIf: skip current test if condition is true
            - skipUnless: skip current test unless condition is true

        Args:
            test_dict (dict): test info

        Raises:
            SkipTest: skip test

        """
        # TODO: move skip to initialize
        skip_reason = None

        if "skip" in test_dict:
            skip_reason = test_dict["skip"]

        elif "skipIf" in test_dict:
            skip_if_condition = test_dict["skipIf"]
            if self.session_context.eval_content(skip_if_condition):
                skip_reason = "{} evaluate to True".format(skip_if_condition)

        elif "skipUnless" in test_dict:
            skip_unless_condition = test_dict["skipUnless"]
            if not self.session_context.eval_content(skip_unless_condition):
                skip_reason = "{} evaluate to False".format(skip_unless_condition)

        if skip_reason:
            raise SkipTest(skip_reason)

hook机制 ¶

类型

setup_hooks:请求前调用的钩子函数,对请求进行预处理
teardown_hooks:请求之后调用的钩子函数,对请求进行后置处理

场景

对testcase生效的钩子
  • testcaseconfig中使用的hook函数,函数在debugtalk.py中自由定义,他的运行是相对于整个测试用例的
    setup_hooks: 在整个用例开始执行前触发 hook 函数,主要用于准备工作
    teardown_hooks: 在整个用例结束执行后触发 hook 函数,主要用于测试后的清理工作
config:
    name: 开发者登录后注册
    base_url: https://api.apiopen.top
    variables:
        developer_name: o_chen
        passwd: 123456
        success_code: 200
    setup_hooks:
        - ${sleep(30)}
    teardown_hooks:
        - ${sleep(30)}
    output:
        - apikey

teststeps:
-
    name: 开发者登录
    api: api/apiopen/developer_login.yml
def sleep(n_secs):
    time.sleep(n_secs)

查看运行日志


用例运行之前

用例运行之后
  • teststeps引用testcase,也可以在节点中定义hook,是对引用的testcase生效的
config:
    name: 开发者登录后注册
    base_url: https://api.apiopen.top
    variables:
        developer_name: o_chen
        passwd: 123456
        success_code: 200

teststeps:
-
    name: 开发者登录
    testcase: testcases/apiopen/login.yml
    setup_hooks:
        - ${sleep(30)}
    teardown_hooks:
        - ${sleep(30)}
对api生效的钩子

api文件中添加hooks节点,将在测试之前和测试之后运行
运行测试时,httprunner会把当前执行的请求和返回储存当变量中,取名requestresponse.当需要对请求和返回做处理时传入对应的变量名称即可.当然,也可以自由的执行其他的函数,在debugtalk.py中编写函数,在hooks中添加调用就行了。

name: 开发者登录
base_url: https://api.apiopen.top
variables:
    developer_name: dev
    passwd: 123456
    success_code: 200
request:
    method: POST
    url: /developerLogin
    data:
        name: $developer_name
        passwd: $passwd
extract:
    apikey: content.result.apikey
validate:
    - eq: [content.code,$success_code]
setup_hooks:
    - ${hook_print($request)}
    - ${sum_two(1,2)}
teardown_hooks:
    - ${hook_print($response)}

运行结果:


在test之前运行

testcase中对引入的api文件添加hooks,将会与原来的文件中的hooks进行合并、覆盖

config:
    name: 开发者登录
    base_url: https://api.apiopen.top
    variables:
        developer_name: o_chen
        passwd: 123456
        success_code: 200

teststeps:
-
    name: 开发者登录
    api: api/developer_login.yml
    setup_hooks:
        - ${sum_two(1,2)}
    teardown_hooks:
        - ${sum_two(3,4)}

运行结果:


合并之后的setup_hooks

合并之后的teardown_hooks

引用

编写 hook 函数 ¶

hook 函数的定义放置在项目的 debugtalk.py 中,在 YAML/JSON 中调用 hook 函数仍然是采用 ${func($a, $b)} 的形式。

setup_hooks ¶

在测试步骤层面的 setup_hooks 函数中,除了可传入自定义参数外,还可以传
$request,该参数对应着当前测试步骤 request 的全部内容。因为 request 是可变
参数类型(dict),因此该函数参数为引用传递,当我们需要对请求参数进行预处理时尤其有用。

teardown_hooks ¶

在测试步骤层面的 teardown_hooks 函数中,除了可传入自定义参数外,还可以传入 $response,该参数对应着当前请求的响应实例(requests.Response)
另外,在 teardown_hooks 函数中还可以对 response 进行修改。当我们需要先对响应内容进行处理(例如加解密、参数运算),再进行参数提取(extract)和校验(validate)时尤其有用。

例:
定义hooks函数修改请求参数和返回数据

def setupRequest(request):
    if request:
        if "data" in request:
            request["data"]["name"] = "O_cheng"

def teardownResponse(response):
    if response:
        # print(response.json)
        # print(response.content)
        # print(response.status_code)
        # print(response.cookies)
        # print(response.elapsed)
        # print(response.encoding)
        # print(response.headers)
        # print(response.history)
        # print(response.ok)
        # print(response.text)
        # print(response.url)
        # print(response.request)
        response.json["code"] = -10000

api脚本中设置hooks函数调用

name: 开发者登录
base_url: https://api.apiopen.top
variables:
    developer_name: dev
    passwd: 123456
    success_code: 200
request:
    method: POST
    url: /developerLogin
    data:
        name: $developer_name
        passwd: $passwd
extract:
    apikey: content.result.apikey
validate:
    - eq: [content.code,$success_code]
setup_hooks:
    - ${setupRequest($request)}
teardown_hooks:
    - ${teardownResponse($response)}

运行脚本文件,查看输出的日志:

  • 运行setup_hooks修改请求参数
    setup_hooks修改请求参数
  • 运行teardown_hooks修改返回参数
    teardown_hooks修改返回参数

扩展

在浏览源码的时候,发现hooks支持将函数的运行结果赋值给变量。源码如下:

    def do_hook_actions(self, actions, hook_type):
        """ call hook actions.

        Args:
            actions (list): each action in actions list maybe in two format.

                format1 (dict): assignment, the value returned by hook function will be assigned to variable.
                    {"var": "${func()}"}
                format2 (str): only call hook functions.
                    ${func()}

            hook_type (enum): setup/teardown

        """
        logger.log_debug("call {} hook actions.".format(hook_type))
        for action in actions:

            if isinstance(action, dict) and len(action) == 1:
                # format 1
                # {"var": "${func()}"}
                var_name, hook_content = list(action.items())[0]
                hook_content_eval = self.session_context.eval_content(hook_content)
                logger.log_debug(
                    "assignment with hook: {} = {} => {}".format(
                        var_name, hook_content, hook_content_eval
                    )
                )
                self.session_context.update_test_variables(
                    var_name, hook_content_eval
                )
            else:
                # format 2
                logger.log_debug("call hook function: {}".format(action))
                # TODO: check hook function if valid
                self.session_context.eval_content(action)

当定义hooks的格式为dict时可以实现将函数返回值添加到变量中

setup_hooks:
    - var: ${func($request)}

但在实际运行过程中,在httprunner检查case时发生异常。具体问题如下: Issues
如果需要使用该功能,可以修改源码,以下修改仅供参考。

单独运行api

api中编写hook将返回值赋值给变量并引用变量报错:httprunner.exceptions.VariableNotFound
修改源码中parser.py,在第958行添加:

        hooks_mapping = {}
        if "setup_hooks" in test_dict:
            for hooks in test_dict["setup_hooks"]:
                if isinstance(hooks, dict):
                    hooks_mapping.update(hooks)

        if "teardown_hooks" in test_dict:
            for hooks in test_dict["teardown_hooks"]:
                if isinstance(hooks, dict):
                    hooks_mapping.update(hooks)
        session_variables_set |= set(hooks_mapping.keys())

hook中定义的变量名称添加到teststep_variables_set中,teststep_variables_set用于校验testcase中引用的变量是否存在,在测试之前检验testcase是否是完整的,避免在测试过程中出现异常,检查testcase完整性的操作将在运行test之前完成

运行testcase

如果在hook将函数的返回值赋值给变量,那么他在hook中将以dict的形式定义如:

setup_hooks:
    - var: ${getRequest($request)}
    - req: ${getRequest($request)}
    - ${setupRequest($request)}
teardown_hooks:
    - ${teardownResponse($response)}
    - ${hook_print($var)}
    - ${hook_print($req)}

在运行测试之前,httprunner会将testcase中定义的hooks与api中定义的hooks进行合并去重,如果hooks中存在dict,将会报错:TypeError: unhashable type: 'dict'.原因是合并hooks使用的是set()方法,在parser.py的第816行

    # merge & override setup_hooks
    def_setup_hooks = api_def_dict.pop("setup_hooks", [])
    ref_setup_hooks = test_dict.get("setup_hooks", [])
    extended_setup_hooks = list(set(def_setup_hooks + ref_setup_hooks))
    if extended_setup_hooks:
        test_dict["setup_hooks"] = extended_setup_hooks

修改之前首先要对合并hooks的方法重新编写,区分dict和普通的hooks,普通的hook合并后去重,dict形式的,相同key名称时,testcase中定义的覆盖api中原因的定义,顺序为dict在前,执行方法并返回值到变量,之后在执行普通hook,他们有可能应用之前定义的变量。
validator.py中添加方法

def extend_hooks(raw_hooks, override_hooks):
    """ extend raw_hooks with override_hooks.
        override_hooks will merge and override raw_hooks.

    Args:
        raw_hooks (list):
        override_hooks (list):

    Returns:
        list: extended hooks

    Examples:
        >>> def_setup_hooks = ["${hook_print($token)}", {"sign": "${getSign($token)}"}]
        >>> ref_setup_hooks = [{"sign": "${getRandomSign($token)}"}, "${hook_print($sign)}"]
        >>> extend_hooks(def_setup_hooks, ref_setup_hooks)
            [
                {'sign': '${getRandomSign($token)}'},
                '${hook_print($token)}',
                '${hook_print($sign)}'
            ]

    """
    extended_hooks_dict = {}
    extended_hooks_list = []

    def separation(hooks):
        for hook in hooks:
            if isinstance(hook, dict):
                extended_hooks_dict.update(hook)
            else:
                extended_hooks_list.append(hook)

    separation(raw_hooks)
    separation(override_hooks)

    extended_hooks = [{k: v} for k, v in extended_hooks_dict.items()]
    extended_hooks.extend(list(set(extended_hooks_list)))
    return extended_hooks

之后将parser.py中合并hooks的地方使用自定义的方法即可

    # merge & override setup_hooks
    def_setup_hooks = api_def_dict.pop("setup_hooks", [])
    ref_setup_hooks = test_dict.get("setup_hooks", [])
    # extended_setup_hooks = list(set(def_setup_hooks + ref_setup_hooks))
    extended_setup_hooks = validator.extend_hooks(def_setup_hooks, ref_setup_hooks)
    if extended_setup_hooks:
        test_dict["setup_hooks"] = extended_setup_hooks

    # merge & override teardown_hooks
    def_teardown_hooks = api_def_dict.pop("teardown_hooks", [])
    ref_teardown_hooks = test_dict.get("teardown_hooks", [])
    # extended_teardown_hooks = list(set(def_teardown_hooks + ref_teardown_hooks))
    extended_teardown_hooks = validator.extend_hooks(def_teardown_hooks, ref_teardown_hooks)
    if extended_teardown_hooks:
        test_dict["teardown_hooks"] = extended_teardown_hooks

将上述两处修改完成后就可以在用例文件中将hooks返回的参数赋值给变量,并在测试中使用这些变量了。


对变量进行赋值

引用setup_hooks中定义的变量

你可能感兴趣的:(httprunner学习实践(五)-- skip机制和hook机制)