HttpRunner HttpRunner 再议参数化数据驱动机制

debugtalk · 2018年03月25日 · 最后由 爻迩love 回复于 2019年01月31日 · 3171 次阅读

《HttpRunner 实现参数化数据驱动机制》一文中,我们实现了参数化数据驱动的需求,并阐述了其设计思路的演变历程和开发实现涉及的核心要素。

问题及思考

经过一段时间的实际应用后,虽然参数化数据驱动的功能可以正常使用,但终究感觉不够优雅。

概括下来,主要有如下 4 个方面。

1、调用方式不够自然,描述方式比较繁琐。

- config:
name: "user management testset."
parameters:
- user_agent: Random
- app_version: Sequential

描述参数取值方式的时候,需要采用SequentialRandom来进行指定是要顺序取值还是随机乱序取值。暂且不说Sequential这个单词大家能否总是保证拼写正确,绝大多数情况下都是顺序取值,却也总是需要指定Sequential,的确会比较繁琐。

2、即使是简单的数据驱动场景,也同样需要准备 CSV 文件,问题复杂化。

指定数据驱动的数据源时,必须创建一个 CSV 文件,并将参数化数据放置在其中。对于大数据量的情况可能没啥问题,但是假如是非常简单的场景,例如上面的例子中,我们只需要对app_version设定参数列表 ['2.8.5', '2.8.6'],虽然只有两个参数值,也同样需要去单独创建一个 CSV 文件,就会显得比较繁琐了。

试想,假如对于简单的参数化数据驱动场景,我们可以直接在 YAML/JSON 测试用例中描述参数列表,如下所示,那就简单得多了。

- config:
name: "user management testset."
parameters:
- user_agent: ['iOS/10.1', 'iOS/10.2', 'iOS/10.3']
- app_version: ['2.8.5', '2.8.6']

3、无法兼顾没有现成参数列表,或者需要更灵活的方式动态生成参数列表的情况。

例如,假如我们期望每次执行测试用例的时候,里面的参数列表都是按照特定规则动态生成的。那在之前的模式下,我们就只能写一个动态生成参数的函数,然后在每次运行测试用例之前,先执行函数生成参数列表,然后将这些参数值导入到 CSV 文件中。想想都感觉好复杂。

既然 HttpRunner 已经实现了在 YAML/JSON 测试用例中调用函数的功能,那为啥不将函数调用与获取参数化列表的功能实现和描述语法统一起来呢?

试想,假如我们需要动态地生成 10 个账号,包含用户名和密码,那我们就可以将动态生成参数的函数放置到 debugtalk.py 中:

def get_account(num):
accounts = []
for index in range(1, num+1):
accounts.append(
{"username": "user%s" % index, "password": str(index) * 6},
)

return accounts

然后,在 YAML/JSON 测试用例中,再使用 ${} 的语法来调用函数,并将函数返回的参数列表传给需要参数化的变量。

- config:
parameters:
- username-password: ${get_account(10)}

实现了这一特性后,要再兼容从 CSV 文件数据源中读取参数列表的方式也很简单了。我们只需要在 HttpRunner 中内置一个解析 CSV 文件的 parameterize 函数(也可以简写为 P 函数),然后就可以在 YAML/JSON 中通过函数调用的方式引用 CSV 文件了。如下例中的 user_id 所示。

- config:
name: "demo"
parameters:
- user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
- user_id: ${P(user_id.csv)}
- username-password: ${get_account(10)}

这样一来,我们就可以优雅地实现参数列表数据源的指定了,并且从概念理解和框架实现的角度也能完成统一,即对于 parameters 中的参数变量而言,传入的都是一个参数列表,这个列表可以是直接指定的,可以是从 CSV 文件中加载的,也可以是通过调用自定义函数动态生成的。

4、数据驱动只能在测试用例集(testset)层面,不能针对单个测试用例(testcse)进行数据驱动。

例如,用例集里面有两个接口,第一个接口是获取 token,第二个接口是创建用户(参考 QuickStart 中的 demo-quickstart-6.json)。那么按照之前的设计,在 config 中配置了参数化之后,就是针对整个测试用例集(testset)层面的数据驱动,使用每一组参数运行的时候都要先执行第一个接口,再执行第二个接口。

这可能就跟我们预期的情况不一样了。假如我们期望的是只针对第二个接口做数据驱动,即第一个接口只需要调用一次获取到 token,然后使用参数列表中的数值分别调用第二个接口创建用户,那么之前的方法就行不通了。

既然有这类需求,因此数据驱动也应该具有作用域的概念。

类似于定义的 variables,定义在 config 中是全局有效的,定义在 test 中就只对当前测试用例有效。同样地,我们也可以针对 parameters 增加作用域的概念,若只需实现对当前用例(testcase)的参数化数据驱动,就可以将 parameters 配置放置到当前 test 中。

新的实现

想法明确了,改造实现也就比较简单了。

从版本 1.1.0 开始,HttpRunner 便支持了上述新的数据驱动方式。详细的使用方法,可参考如下使用说明文档:

http://cn.httprunner.org/advanced/data-driven/

至此,HttpRunner 的数据驱动机制就比较完善和稳定了,应该可以解决绝大多数数据驱动场景的需求。

遗留问题

不过,还有一类场景暂时没有实现支持,即需要根据先前接口返回结果来对后续接口进行数据驱动的情况。

以如下场景为例:

  • 加载用户列表,获取当前用户列表中的所有用户;
  • 依次对每一个用户进行点赞或者发送消息的操作。

这和前面的第三条有点类似,都需要先动态获取参数列表,然后再使用获取得到的参数列表进行数据驱动。但也存在较大的差异,即获取用户列表的操作也是测试场景的一部分,并且通常因为需要共享 session 和 cookies,因此不能将第一步的请求放置到 debugtalk.py 中。

之前的一个想法是,在第一个接口中,将结果返回的用户列表提取(extract)出来保存至变量(user_list),然后在后续需要做数据驱动的接口中,在 parameters 中引用前面提取出的用户列表($user_list);若有需要,还可以自定义函数(parse_users),将前面提取出来的用户列表转换至框架支持的格式。

- test:
name: load user list
request: {...}
extract:
- user_list: content.users

- test:
name: send message to user
parameters:
- user: ${parse_users($user_list)}
request: {...}

这个方式乍一看是可行的,但实际却是行不通的。

问题在于,在 HttpRunner 的数据驱动机制中,采用参数列表构造测试用例是在初始化阶段,做的工作主要是根据参数列表中的数据生成测试用例并添加至 unittest 的 TestSuite 中,此时测试用例还没有进入执行环节,因此也没法从接口的响应结果中提取参数列表。

若非要解决这个问题,针对 test 的数据驱动,可以将解析 parameters 的实现放置到 request 中;这的确可以实现上述场景中的功能,但在测试用例执行统计方面就会出现问题。以该场景为例,假如获取到的用户列表有100个用户,那么整个用例集将执行101次测试用例,但最终生成的测试报告中却只会展示运行了2条测试用例。

针对该场景,我还没有想到很好的解决方案,暂且将其作为一个遗留问题吧。若你有比较好的实现方案,欢迎反馈给我,或者直接提交 PR

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 20 条回复 时间 点赞
debugtalk HttpRunner 实现参数化数据驱动机制 中提及了此贴 03月25日 16:11

从 1.1.0 版本开始,数据驱动机制进行了较大的优化和调整。对于文末的遗留问题,希望大家能给些建议。

数据传递要参数化其实也是可以的,有多种方案进行,因为接口的发送及响应的key正常来说都是知道的,所以就是key的value进行传递下去,可以走内存也可以走数据库或者其他的都行,执行层面并不会有统计错误方面的问题,不过我们是没有用框架,统计结果都是自己写的

如果只是统计方面的问题,可以考虑下参考 testng data provider 的方式,针对列表中的每个参数单独在执行器里触发测试方法?这样统计起来就是多条用例了。

@kasi @chenhengjie123 这里的主要问题在于,unittest 构造测试用例是在执行之前,而开始执行的时候 unittest 中的用例数就确定了。估计还有个办法,就是测试用例执行完之后,再去修改 unittest 的实例,但因为不光是统计数字,还包括具体每一项测试用例的详细内容(请求和响应),因此要改动的量会比较大。

debugtalk 回复

是的,被unittest本身给限制了,你这样的改动还不如直接去除unittest

卡斯 回复

如果不用 unittest,那就要自己造轮子了,所以这个功能特性还是暂且不做吧。


对于这种关联参数,样可以单独使用username,或者单独使用password呢?因为很多时候一个account会对应一个sn,是相关联的,但是要分开写在header的 不同参数中

wuhao 回复

更新了最新版后,解决。
用$username和$password可以获取对应变量值,由于用的之前版本的HttpRunner,老版本这里有点问题

debugtalk HttpRunner 实现参数化数据驱动机制 中提及了此贴 05月04日 12:17

现在这种形式
parameters:
- user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
- user_id: ${P(user_id.csv)}

如果我要指定取值方式要怎么操作呢。
试了下之前的这种方式,
parameters:
- email-uId-token: Unique
是会提示httprunner.exception.ParamsError: parameters syntax error!得到

梦梦GO 回复

之前的方式已经不适用了,而且也没有支持过 Unique 这种方式,只支持顺序取值和乱序取值。

parameters:
- user_id: ${P(user_id.csv, random)}

请教一个问题:
在variables中可以引用parameters中的参数吗,因为要在request中使用variables作为传参调用一个函数
代码如下:

- test:
name: query
parameters:
- status: [0, 1]
variables:
params:
action: query
serviceProviderId: 53108a8a12c32da8
status: $status
request:
url: /electricitySafety
data: ${get_signature($params)}

报错如下:
httprunner.exception.VariableNotFound: status not found in recursive upward path!
请问这样的写法是不支持吗?还是我写错了,怎样才能在variables中引用parameters中的参数?

parameters:
- user_id: ${P(user_id.csv)}
其中csv文件可以做成可指定绝对地址的不?在实际应用中,同一个csv文件可能多个接口会引用到,希望可以统一管理数据。

riku 回复

1.5.11后就不支持test中写parameters了

马尾 回复

亲,你这个问题解决了吗

楼主,
parameters:

  • user_id: ${P(user_id.csv)}可以正常执行
  • user_id: ${P(.\resources\user_id.csv)} 就找不到了,为什么只能放在跟case同级的目录,不能统一管理吗
WangYuan 回复

你用的是 Windows 系统吧?文件路径不支持反斜杠。
2.0 版本马上就要发布了,你后面用 2.0 再试下

你好楼主,
我在抓包后,接口里面的params参数中有where的参数,里面有$in 来过滤查询,hrun后这里无法识别in ,会报错。请问怎么解决

"params": {
"sort": "-_created",
"max_results": "20",

"where": "{\"grade\":{\"$in\":[\"5b65a4840a0128a9d4505bd2\",\"5b65a4710a0128a9d4505bd1\",\"5b65a45f0a0128a9d4505bd0\"]}}",
"page": "1"
},
"method": "GET"
},

$in 为了区别外部导入的参数,我在$前面加了转意符 还是报错

debugtalk 回复

是的,我是windows系统

请问下,怎样将请求响应中的值(如token)设置为全局变量,让其他文件中的测试用例都可以引用?
目前的实现思路是:先抽取响应中的值, 然后调用函数,将该值保存在一个自定义的配置文件中。 目前遇到的问题有:
1,在用例的validate中调用函数,保存该值,但设置多个全局变量时,出现问题,只能保存最后一个。
2,在用例的teardown_hooks中调用函数,但不能引用extract中的变量

目前,postman 中可以方便的设置全局变量,使用:pm.environment.set("token", token);就可以了。

想问下有没有什么方式可以方便的设置全局变量?

你好,我配置了parameters,但是发现参数化没有生效,麻烦看看
python:3.6
httprunner:2.0
脚本:

  • config: name: 登录 parameters: - account: ${P(account.csv)}
  • test: name: 账号$account

运行结果:
把“账号$account”原文打印出来了,没有打印出来具体值

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