专栏文章 HttpRunner 的测试用例分层机制 (适用于 2.X)

debugtalk · 2019年04月18日 · 最后由 梦梦GO 回复于 2020年04月15日 · 8058 次阅读
本帖已被设为精华帖!

背景描述

《HttpRunner 2.0 正式发布》中提到,HttpRunner 从 2.0 版本开始,对测试用例组织形式进行了较大的调整,更正了之前在自动化测试概念上的偏差。

对应地,测试用例分层机制也进行了重新设计,因此在概念和使用方法方面都会出现很大的差异。本文便是对新的测试用例分层机制进行介绍。

测试用例分层模型

关于为什么要使用测试用例分层机制,在《HttpRunner 的测试用例分层机制(已过期)》中已经进行了详细的介绍,虽然使用方法变化了,但原理上都是相同的。

概括来说,测试用例分层机制的核心是将接口定义、测试步骤、测试用例、测试场景进行分离,单独进行描述和维护,从而尽可能地减少自动化测试用例的维护成本。

逻辑关系图如下所示:

同时,强调如下几点核心概念:

  • 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
  • 测试用例(teststep)是测试步骤的 有序 集合,每一个测试步骤对应一个 API 的请求描述
  • 测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理

如果对于上述第三点感觉难以理解,不妨看下上图中的示例:

  • testcase1 依赖于 testcase2,那么就可以在测试步骤(teststep12)中对 testcase2 进行引用,然后 testcase1 就是完整且可独立运行的;
  • 在 testsuite 中,testcase1 与 testcase2 相互独立,运行顺序就不再有先后依赖关系了。

分层描述详解

理解了测试用例分层模型,接下来我们再来看下在分层模型下,接口、测试用例、测试用例集的描述形式。

接口定义(API)

为了更好地对接口描述进行管理,推荐使用独立的文件对接口描述进行存储,即每个文件对应一个接口描述。

接口定义描述的主要内容包括:name、variables、request、base_url、validate 等,形式如下:

name: get headers
base_url: http://httpbin.org
variables:
    expected_status_code: 200
request:
    url: /headers
    method: GET
validate:
    - eq: ["status_code", $expected_status_code]
    - eq: [content.headers.Host, "httpbin.org"]

其中,name 和 request 部分是必须的,request 中的描述形式与 requests.request 完全相同。

另外,API 描述需要尽量保持完整,做到可以单独运行。如果在接口描述中存在变量引用的情况,可在 variables 中对参数进行定义。通过这种方式,可以很好地实现单个接口的调试。

$ hrun api/get_headers.yml
INFO     Start to run testcase: get headers
headers
INFO     GET http://httpbin.org/headers
INFO     status_code: 200, response_time(ms): 477.32 ms, response_length: 157 bytes

.

----------------------------------------------------------------------
Ran 1 test in 0.478s

OK

测试用例(testcase)

引用接口定义

有了接口的定义描述后,我们编写测试场景时就可以直接引用接口定义了。

在测试步骤(teststep)中,可通过 api 字段引用接口定义,引用方式为对应 API 文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 debugtalk.py 所在的目录路径。

- config:
    name: "setup and reset all."
    variables:
        user_agent: 'iOS/10.3'
        device_sn: "TESTCASE_SETUP_XXX"
        os_platform: 'ios'
        app_version: '2.8.6'
    base_url: "http://127.0.0.1:5000"
    verify: False
    output:
        - session_token

- test:
    name: get token (setup)
    api: api/get_token.yml
    variables:
        user_agent: 'iOS/10.3'
        device_sn: $device_sn
        os_platform: 'ios'
        app_version: '2.8.6'
    extract:
        - session_token: content.token
    validate:
        - eq: ["status_code", 200]
        - len_eq: ["content.token", 16]

- test:
    name: reset all users
    api: api/reset_all.yml
    variables:
        token: $session_token

若需要控制或改变接口定义中的参数值,可在测试步骤中指定 variables 参数,覆盖 API 中的 variables 实现。

同样地,在测试步骤中定义 validate 后,也会与 API 中的 validate 合并覆盖。因此推荐的做法是,在 API 定义中的 validate 只描述最基本的校验项,例如 status_code,对于与业务逻辑相关的更多校验项,在测试步骤的 validate 中进行描述。

引用测试用例

在测试用例的测试步骤中,除了可以引用接口定义,还可以引用其它测试用例。通过这种方式,可以在避免重复描述的同时,解决测试用例的依赖关系,从而保证每个测试用例都是独立可运行的。

在测试步骤(teststep)中,可通过 testcase 字段引用其它测试用例,引用方式为对应测试用例文件的路径,绝对路径或相对路径均可。推荐使用相对路径,路径基准为项目根目录,即 debugtalk.py 所在的目录路径。

例如,在上面的测试用例("setup and reset all.")中,实现了对获取 token 功能的测试;同时,在很多其它功能中都会依赖于获取 token 的功能,如果将该功能的测试步骤脚本拷贝到其它功能的测试用例中,那么就会存在大量重复,当需要对该部分进行修改时就需要修改所有地方,显然不便于维护。

比较好的做法是,在其它功能的测试用例(如创建用户)中,引用获取 token 功能的测试用例(testcases/setup.yml)作为一个测试步骤,从而创建用户("create user and check result.")这个测试用例也变得独立可运行了。

- config:
    name: "create user and check result."
    id: create_user
    base_url: "http://127.0.0.1:5000"
    variables:
        uid: 9001
        device_sn: "TESTCASE_CREATE_XXX"
    output:
        - session_token

- test:
    name: setup and reset all (override) for $device_sn.
    testcase: testcases/setup.yml
    output:
        - session_token

- test:
    name: create user and check result.
    variables:
        token: $session_token
    testcase: testcases/deps/check_and_create.yml

测试用例集(testsuite)

当测试用例数量比较多以后,为了方便管理和实现批量运行,通常需要使用测试用例集来对测试用例进行组织。

在前文的测试用例分层模型中也强调了,测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理。

因为是 无序 集合,因此测试用例集的描述形式会与测试用例有些不同,在每个测试用例集文件中,第一层级存在两类字段:

  • config: 测试用例集的总体配置参数
  • testcases: 值为字典结构(无序),key 为测试用例的名称,value 为测试用例的内容;在引用测试用例时也可以指定 variables,实现对引用测试用例中 variables 的覆盖。

非参数化场景

config:
    name: create users with uid
    variables:
        device_sn: ${gen_random_string(15)}
        var_a: ${gen_random_string(5)}
        var_b: $var_a
    base_url: "http://127.0.0.1:5000"

testcases:
    create user 1000 and check result.:
        testcase: testcases/create_user.yml
        variables:
            uid: 1000
            var_c: ${gen_random_string(5)}
            var_d: $var_c

    create user 1001 and check result.:
        testcase: testcases/create_user.yml
        variables:
            uid: 1001
            var_c: ${gen_random_string(5)}
            var_d: $var_c

参数化场景(parameters)

对于参数化场景,可通过 parameters 实现,描述形式如下所示。

config:
    name: create users with parameters
    variables:
        device_sn: ${gen_random_string(15)}
    base_url: "http://127.0.0.1:5000"

testcases:
    create user $uid and check result for $device_sn.:
        testcase: testcases/create_user.yml
        variables:
            uid: 1000
            device_sn: TESTSUITE_XXX
        parameters:
            uid: [101, 102, 103]
            device_sn: [TESTSUITE_X1, TESTSUITE_X2]

参数化后,parameters 中的变量将采用笛卡尔积组合形成参数列表,依次覆盖 variables 中的参数,驱动测试用例的运行。

文件目录结构管理 && 脚手架工具

在对测试用例文件进行组织管理时,对于文件的存储位置均没有要求和限制,在引用时只需要指定对应的文件路径即可。但从约定大于配置的角度,最好是按照推荐的文件夹名称进行存储管理,并可通过子目录实现项目模块分类管理。

总结如下:

  • debugtalk.py 放置在项目根目录下,假设为 PRJ_ROOT_DIR
  • 接口定义(API)放置在 PRJ_ROOT_DIR/api 目录下
  • 测试用例(testcase)放置在 PRJ_ROOT_DIR/testcases 目录下
  • 测试用例集(testsuite)文件必须放置在 PRJ_ROOT_DIR/testsuite 目录下

目录结构如下所示:

$ tree tests
tests
├── api
│   ├── create_user.yml
│   ├── get_headers.yml
│   ├── get_token.yml
│   ├── get_user.yml
│   └── reset_all.yml
├── debugtalk.py
├── testcases
│   ├── create_user.yml
│   ├── deps
│   │   └── check_and_create.yml
│   └── setup.yml
└── testsuites
    ├── create_users.yml
    └── create_users_with_parameters.yml

同时,在 HttpRunner 中实现了一个脚手架工具,可以快速创建项目的目录结构。该想法来源于 Djangodjango-admin.py startproject project_name

使用方式也与 Django 类似,只需要通过 --startproject 指定新项目的名称即可。

$ hrun --startproject demo
Start to create new project: demo
CWD: /Users/debugtalk/MyProjects/examples

created folder: demo
created folder: demo/api
created folder: demo/testcases
created folder: demo/testsuites
created folder: demo/reports
created file: demo/debugtalk.py
created file: demo/.env

相关参考

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 45 条回复 时间 点赞

终于出来了

simple 回复

不好意思久等了😅

引用 API 只能在 teststep 下引用么,可以只在 testcase 里引用么

新手请教楼主大神几个问题哈:
1、在之前的文档里,api 里面的 yaml 文件,可以包含多个接口,以-api 块区分不同的接口,现在是单个 yaml 对应一个接口吗?
2、对于每个接口都需要的参数,比如 os_platform,这个放在那个地方可以让我不重复添加这个参数呢?还有对于 token 这种跨用例跨场景的,怎么可以让我只登陆一次就可以?
3、hooks 方法在 api 里面不生效吗?
4、如果我所有的 api 都依赖 login 返回的 userId 和对应的 token,那在 api 里面,我该怎么处理这个呢?

思寒_seveniruby 将本帖设为了精华贴 04月20日 20:15

楼主,怎么出来把登录的 token 封装起来,提供给其他接口调用

duanchangliang 回复

你怎么在 testcase 里引用?teststep 本来就是在 testcase 里的啊

buggg 回复

1、是的;其实在当前版本,也可以一个文件里写多个接口,格式跟之前相同,但只是为了保留部分兼容性,不推荐;
2、你说的是 headers 里公共参数么?这个之前是支持在 config 的 headers 里写的,后来去掉了;但从大家的反馈来看,这个后面还是得加回去;对于你说的跨场景的情况,那么就是要将多个用例组装成一个新的用例,在这个新的用例里面是共享 session 的;
3、hooks 写在 api 定义里面应该也是可以的,你尝试发现不行么?
4、针对这种情况,在 API 定义里面也没办法,传参就是需要将从 login 返回的 token 传进去。

winterswimming 回复

将获取 token 的脚本封装为一个用例 A,在其它用例里面,将 A 作为一个步骤,获取 A output 的 token

debugtalk 回复

刚上手,问下大佬几个问题😃
1、多个测试用例是写一个 json 文件好还是分开写好,分开的话 config 需要声明多个吗
2、有些接口 response 返回多个 json 数据,如果指定提取一个字段的话是使用这个方法吗"userId": "json.body.userBasicInfo.userId[1]"

3、文件上传接口参数应该怎么声明
4、针对点赞,评论接口断言,code 响应 200 为验证是否成功还需重新调用详情接口与上次 count 做比较,如何使用这两个参数值来 eq

我想问下 httprunner 主要是针对接口自动化测试吗?

初痕 回复

你用的是什么编辑器来写 yaml 呢,看起来蛮可以啊,求介绍!

请问 2.0 是不是不支持直接引用 debugtalk.py 中的变量了?
之前的版本:
debugtalk.py 中:
api 中:

2.0 后是否不支持这样的用法了?

debugtalk 回复

config 里的 id 是什么,output 是写在 config 里的吗

debugtalk 回复

请问结合 Jenkins 构建的话 shell 命令为 hrun d:\python3.7\Lib\site-packages\httprunner\login.json --report-dir d:\reports
执行成功了但是没有输出 html 报告,cmd 命令可以输出,这是什么原因

debugtalk 回复

感谢楼主,第三个问题,我后来找到了,谢谢。
第二个问题说的公共参数,如果是在 config 里面写,那么意味着每个 yaml 文件里都需要写对吧?有没有一个更方便的方法,只需要在某个地方写,就代表着整个项目里所有的接口都会带上这个参数呢?

初痕 回复

我好像在 Jenkins 执行的时候根本没执行最后 hrun 命令那一行

支持楼主,很好用的框架,一天就上手监控手中的项目了,顺便问一下,有交流群吗?还有就是什么时候能再更新一版文档说明?

楼主,httprunner 有没有支持全局变量的设置啊
现在遇到的问题就是,
第一个 testcase 中 extract 提取到的值没法 i 给第二个第三个 testcase 去用 (不是 token),就是将其他的 testcase 作为一个 teststep 合并为一个用例 testcase,
现在仅能一个 testcase 下用,这样不是和用例分层机制避免重复背道而驰了吗
利用 debugtalk 将提取的值在存储在本地上,再去读,这样之前涉及到调用到该参数的地方又得大量去改
很急啊,了解 httpruner 的人都问了一圈了

大神,想问下:
1、api 里写 def 函数和不写有什么区别?
2、如果 api 写了 def,那么这个函数能用在 debugtalk 里吗?
3、如果 api 写了 def,那么 test 里就可以直接使用这个函数,那是不是所有的 api 里的函数都是唯一的了!

我想请教一下。目前我们这边接口加签 POST 请求是整个 body 进行加密然后生成一个 sign 放到 header 里面。
这种情况在使用 httprunner 中有什么办法可以把整个 body 作为参数去调用 debugtalk 里面的函数么?

请教一下楼主,我在 jenkins 里面构建的时候,没有执行 hrun 这一句,不知道为什么。

一个 API 定义就是一个文件,这个感觉没有之前的使用方便

想了解一下实现原理可以不,表示看不懂


使用这样的方式为什么不能替换参数

一个接口必须先依赖于登录接口,在接口定义中该如何定义

请问 大佬 用例文件中所需要的字段有 完整的列举吗?api、testcase、config、test、testsuit

yjq 回复

我觉得可以在 debugtalk 文件中添加一个 setup_hook 函数,函数里面可以使用 request 做参数,应该可以去到请求的参数,然后在进行加密

winterswimming 回复

emmm,${} 这个里面应该只能写入函数,你这样的写法框架可能甚至识别不到你所调用的方法
你想取出这个函数所返回的列表里面的固定 index 的数据的话建议在函数里面添加参数控制

winterswimming 回复

我理解 作者对于 api 的定义就是单个接口
如果接口间有依赖的话应该是要整合在一个 testcase 里面,添加多个 teststep 来实现

33楼 已删除
34楼 已删除

/Users/debugtalk/MyProjects/HttpRunner-dev/httprunner/tests
$ hrun testcases/setup.yml
INFO Loading environment variables from /Users/debugtalk/MyProjects/HttpRunner-dev/HttpRunner/tests/.env
ERROR !!!!!!!!!! exception stage: load tests !!!!!!!!!!
ModuleNotFoundError: No module named 'tests.api_server'
请问下这个错误如何处理,有大佬知道么

testcase 中引用 api 报错找不到请问是什么原因?我确定 api 文件夹下是有 get_token.yml 的,我下的 GitHub 上的样例文件夹
httprunner.exceptions.ApiNotFound: api/get_token.yml not found!

wuhong1014 回复

碰到了同样的问题,切换到 yml 文件所在目录后,运行成功。

想问下大佬,字符串的切片在 httprunner 里面怎么用,直接用 [4:9] 方式不行呢,还是说要在热插拔里面重写函数

大家好,初学者来了。我想问下 testsuites 中的 parameters 怎么接收 csv 测试文件中的列表,比如以下是 csv 中测试数据,我想将 [eth10.1,eth9.1] 以列表形式给 nodeAmember,要怎么操作?是不是这样写不对?
groupId,groupName,nodeAip,nodeZip,nodeAmember,nodeZmember,masterLinkLabel
1,group-test-1,10.240.30.193,10.240.30.194,[eth10.1,eth9.1],[eth10.1,eth9.1],193-194-link-2
2,group-test-2,10.240.30.193,10.240.30.194,[eth9.2,eth10.2],[eth9.2,eth10.2],10.240.30.193eth9.2-10.240.30.194eth9.2

你好,关于 httprunner 做性能测试时想请问一下是每一条用例单独执行 locusts 吗?有多条测试用例同时执行的方法吗,比如循环读取用例然后执行?

九毫兄你好 , 咨询你个问题,就是你的这些 json\yaml 文件最后你是如何组织的呢?比如 A 接口需要 B 接口的响应数据,你的 hrunner 是通过参数化来实现了接口数据的管理,但是你是如何来控制 B 接口一定会在 A 接口前执行呢? 目前我这边考虑过的方案一种是类似 unitest 这种通过 test_number 编号来排序,还有一种就是给 json 文件夹一个 tag 来判断是否有一个前置接口,可是两种情况 y 一个需要测试人员对接口的依赖关系和执行方面特别熟悉,然后才能有效排序, 另一个会增加 json 文件中的复杂度,特别是如果存在嵌套依赖的话

大佬,引用接口定义的时候,总是路径不对,绝对路径和相对路径都不行,麻烦指教

Glayfang 回复

我也遇到了。解决办法:路径切换到 api 的路径,再执行 case 通过了。感觉是框架的大 bug!

debugerliu 回复

切换到 api 的路径在执行 case,通过了。感觉是框架的 bug

仅楼主可见
namekkozZ 回复

我目前有一个问题就是:
用例 A 引用了获取 token 的用例,之后执行一些新建操作
用例 B 又需要引用用例 A
那在用例 B,还需要引用获取 token 的用例吗?还是在引用用例 A 的时候 output 出来?

假如每个用例都依赖获取 token 的用例,那在每个用例前都引用获取 token 的用例,假设 100 个用例,那不就执行了 100 次登录 - 获取 token 的操作了。
我一直都很迷惑这里,我的理解是,获取 token 是只用执行一次的

有计划发 3.0 的文档吗
更新后发现跑不起来 是 case 的 config 里面必须要有 name 字段
不知道还有别的变动没 有点慌

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册