HTTPRunner
概要
HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份YAML/JSON脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。
安装
pip install httprunner(推荐使用python3版本)
启动demo server
- cd到httprunner的目录,set FLASK_APP=/httprunnerWorkspace/xxxapi_server.py文件地址(非win系统用export)
- flask run命令执行后,即可启动demo server并通过browser访问
使用流程
- 网页访问时,通过postman interceptor/Charles/firefox或者chrome的开发者工具来捕获相关的请求详情。
- 用json/yaml编写测试用例。
- cmd执行测试用例hrun xxxx.json
- reports目录下自动生成测试后的结果报告
支持脚本录制
- 使用Charles>Sequence录制并导出为.har格式
- 转换生成测试用例 har2case file_path/xxxx.har new_name.json(也可指定为.yml格式),如果第二个参数省略则默认为json格式
- 执行如上脚本即可生成测试报告
目录结构(分层原理)
约定大于配置的原则
在组织测试用例描述的文件目录结构时,遵循约定大于配置的原则。通过hrun --startproject project_name创建测试项目目录后,目录结构如下:
- api:API接口定义必须放置在api目录下
- suite:模块定义必须放置在suite目录下
- testcases:测试场景文件必须放置在testcases目录下
- debugtalk.py: 相关的函数定义放置在debugtalk.py中
结构关系
- api>suite>testcases(在api中定义基本接口及操作,suite中定义suite和调用的api,testcases中直接调用api或者是suite使用)
[ { "api": { "def": "event_add($eid, $name)", "validate": [ {"eq": ["status_code", 200]}, {"eq": ["content.message", "add event success"]} ], "request": { "url": "/api/add_event/", "method": "POST", "data": { "status": 1, "limit": 1000, "name": "$name", "address": "testemail@email.com", "start_time": "2018-09-18 16:00:00", "eid": "$eid" } } } }, { "api": { "def": "event_get($eid, $name)", "validate": [ {"eq": ["status_code", 200]}, {"eq": ["content.message", "success"]} ], "request": { "url": "/api/get_event_list/", "method": "GET", "params": { "eid": "$eid", "name": "$name" } } } }] |
[ { "config": { "request": { "base_url": "http://127.0.0.1:8000" }, "name": "event add get test", "def": "event_add_get_suite($eid, $name)" } }, { "test": { "validate": [ {"eq": ["status_code", 200]}, {"eq": ["content.message", "add event success"]} ], "api": "event_add($eid, $name)", "name": "event add" } }, { "test": { "validate": [ {"eq": ["status_code", 200]}, {"eq": ["content.message", "success"]}], "api": "event_get($eid, $name)", "name": "event get" } }] |
[ { "config": { "name": "event test", "request":{ "base_url": "http://127.0.0.1:8000/" }, "parameters": [ {"eid-name": "${P(testcases/eventInfo.csv)}"} ] } }, { "test": { "suite": "event_add_get_suite($eid, $name)", "name": "event add get details test" } }] |
常用命令
|
命令
|
详情
|
备注
|
|---|---|---|
| httprunner/hrun | hrun -h | 核心命令, 通过-h查看命令详情 |
| hrun --validate xxxx.json | json格式文件的正确性检测 | |
| hrun --validate xxxx1.json xxxx2.json xxx3.yml | 也可以同时指定多个文件,并同时支持json/yaml类型的正确性检测 | |
| hrun xxx.json --failfast | 默认情况会执行所有指定用例集中的所有测试用例,--failfast命令遇到失败时不再执行后续用例 | |
| hrun xxx.json --log-level debug | 查看请求的参数和相应的详细内容,可将日志级别设置为debug | |
| hrun xxx.json --html-report-name new_name | 指定生成的报告名称 | |
| har2case | ... | 格式转换命令,将har格式转换成json/yaml格式 |
脚本结构详细
|
yml
|
json
|
|---|---|
- config:
name: "xxxxxxx"
parameters:
- user_agent: ["iOS/10.1", "iOS/10.2", "iOS/10.3"] //列表参数化
- app_version: ${P(app_version.csv)} //csv参数化
- os_platform: ${get_os_platform()} //自定义函数参数化
variables:
- user_agent: 'iOS/10.3'
- device_sn: ${gen_random_string(15)}
- os_platform: 'ios'
- app_version: '2.8.6'
request:
base_url: http://127.0.0.1:5000
headers:
Content-Type: application/json
device_sn: $device_sn
- test:
name: get token with $user_agent, $os_platform, $app_version
request:
url: /api/get-token
method: POST
headers:
app_version: $app_version
os_platform: $os_platform
user_agent: $user_agent
json:
sign: ${get_sign($user_agent, $device_sn, $os_platform, $app_version)}
extract:
- token: content.token
validate:
- eq: [status_code, 200]
- eq: [headers.Content-Type, application/json]
- eq: [content.success, true]
|
[ {
"config": {...}
},
{
"test": {...}
},
{
"test": {...}
}
]
|
"config": { "name": "test report title", //required; string; 测试用例集的名称,测试报告的标题 "parameters": [ //optional; list of dict; 全局参数,用于实现数据化驱动,作用域为整个测试用例集 {"user_agent": ["ios/10.1", "ios/10.2", "ios/10.3"]}, {"app_version": "${P(app_version.csv)}"}, {"os_platform": "${get_os_platform()}"} ], "variables": [ //optional; list of dict; 定义的全局变量,作用域为整个测试用例集 {"user_agent": "ios/10.3"}, {"device_sn": "${gen_random_string(15)}"}, {"os_platform": "ios"} ], "request":{ //optional; dict of dict; request的公共参数,常用参数包括base_url和headers "base_url": "http://127.0.0.1:5000", "headers": { "Content-Type": "application/json", "device_sn": "$device_sn" } }, "output": [ //optional; list of string; 整个用例集输出的参数列表,可输出的参数包括variable和extract的参数; 在log-leve为debug模式下,会在terminal中打印出参数内容 "token" ]} |
"test": { "name": " test case title and support $os_platformxxxx", //required; string; 测试用例的名称,测试报告中将作为每一项测试的标题 "request": { //required; dict of dict; HTTP请求的详细内容 "url": "/api/get-token", "method": "POST", "headers": { "app_version": "$app_version", "os_platform": "$os_platform", "user_agent": "$user_agent" }, "json": { "sign": "$user_agentxxxx" }, "extract": [ //optional; list of dict; 从当前HTTP请求的相应结果中提取参数,并保存到参数变量中(如token),后续测试用例可以通过&token的形式进行引用 {"token": "content.token"} ], "validate": [ //optional; list of dict; 测试用例中定义的结果校验项。 {"eq": ["status_code": 200]}, {"eq": ["headers.Content-Type", "application/json"]}, {"eq": ["content.success", true]} ], "setup_hooks": [], //optional; list of string; 在HTTP请求发送前执行hook函数,主要用于准备工作。hook函数放置于debugtalk.py中,必须包含三个参数method,url,kwargs(request的参数字典) "teardown_hooks": [] //optional; list of string; 在HTTP请求发送后执行hook函数,主要用于测试后的清理工作。hook函数放置于debugtalk.py中,必须包含一个参数,resp_obj:requests.Response实例 }} |
测试用例文件说明
- 测试用例集:单个测试用例或多个测试用例的集合,表现形式为一个json文件
- 测试用例:单次http请求和响应过程,表现形式为json文件中的一个test
- config:全局配置项,作用于整个测试用例集
- test:作用于单个测试用例
- 如果一个变量在config中定义了,在test中没有定义,则test会继承该变量
- 如果一个变量在config和test中都定义了,则test会使用自己定义的变量值
- 各个test的空间相互独立,互不影响
- 如果在多个test之前传递参数值,则需要使用extract关键字,并且只能从前向后传递
- 测试用例存在顺序关系,运行时从前往后一次运行
脚本详细说明
-
extract(结果提取器)
参数提取功能关键字extract。如token权限验证问题,想让后续的test的token也使用前面test中的token信息时,及可通过参数提取extract。extract"extract": [{"token": "content.token"}],......"headers": {"token": "$token",........},只要返回结果是json类型,就可以将其中的任意字段进行提取,并保存到一个变量中,方便后续接口请求进行引用。
*http://cn.httprunner.org/testcase/structure/
Validator详细
|
No.
|
Comparator
|
Description
|
A(check), B(expect)
|
|---|---|---|---|
| 1 | eq | value is equal | A == B |
| 2 | lt | less than | A < B |
| 3 | le | less than or equals | A <=B |
| 4 | gt | greater than | A >B |
| 5 | ge | greater than or equals | A >=B |
| 6 | ne | not equals | A != B |
| 7 | str_eq | string equals | str(A) == str(B) |
| 8 | len_eq, count_eq | length or count equals | len(A) == B |
| 9 | len_gt,count_gt | length greater than | len(A) > B |
| 10 | len_ge,count_ge | length greater than or equals | len(A) >= B |
| 11 | len_lt, count_lt | length less than | len(A) < B |
| 12 | len_le, count_le | length less than or equals | len(A) <= B |
| 13 | contains | contains |
[1,2] contains 1 |
| 14 | contained_by | contained by | A in B |
| 15 | type_match | A is instance of B | isinstance(A, B) |
| 16 | regex_match | regex matches | re.match(B, A) |
| 17 | startwith | start with | A startwith(B) is True ('abc' startwith 'ab') |
| 18 | endswith | ends with | A endswith(B) is True ('abc' endswith 'bc') |
脚本参数化–未完待续
定义参数时,需要使用parameters关键字。
- $变量转义符
- ${}函数转义符,可以直接填写函数名称及调用参数,还可以包含变量
列表参数化
|
yml
|
有关联的yml列表参数化
|
json
|
有关联的json列表参数化
|
|---|---|---|---|
- config:
parameters:
- var1: ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
- test:
|
- config:
parameters:
- var1-var2:
- test:
|
[ {
"config": {
|
[ {
"config": {
|
CSV参数化
- config: parameters: - eid-name: ${P(add.csv)}//关联参数- test: request: data: address: dalian eid: '$eid' limit: '1000' name: '$name' start_time: '2018-07-17 18:00:00' status: '1' method: POST validate: - eq: - status_code - 200 - eq: - content.message - add event success |
"config": { "parameters": [ { "eid-name": "${P(add.csv)}" //关联参数 } ] }......."test": { "validate": [ { "eq": ["status_code",200] }, { "eq": [ "content.status", 200 ] }, { "eq": [ "content.message", "add event success" ] } ], "request": { "data": { "status": "1", "limit": "1000", "name": "$name", "address": "dalian", "start_time": "2018-07-17 18:00:00", "eid": "$eid" }, "method": "POST" }, "name": "/api/add_event/" } |
自定义函数参数化
debugtalk.py中自定义参数,脚本中使用参数后,报错
httprunner.exceptions.FunctionNotFound: get_parame_info not found in debugtalk.py module!
module mapping: {'variables': {}, 'functions': {}}
关注这个问题,需要持续关注https://testerhome.com/opensource_projects/httprunner上对DarkLi的回答
热加载机制
某些校验功能没有实现时,需要使用自定义的函数。
从热加载的顺序可以看出,查找变量或函数的顺序是从测试用例所在目录开始,沿着父路径逐层往上,直到系统的根目录。
因此,我们可以利用这个优先级原则来组织我们的用例和依赖的Python函数模块。将不同模块的测试用例集文件放在不同的文件夹下:
针对各个模块独有的依赖函数和变量,可以放置在对应文件夹的debugtalk.py文件中;而整个项目公共的函数和变量,就可以放置到项目文件夹的debugtalk.py中。
注意:因为我们在框架运行过程中需要将debugtalk.py作为函数模块进行导入,因此我们首先要保障debugtalk.py满足Python模块的要求,
也就是在对应的文件夹中要包含__init__.py文件
- 导入python模块的关键词:import_module_items
- 不需要显示指定导入的python模块路径,热加载机制会自动发现。
Skip机制(分组执行控制测试用例)
unittest单元测试框架中的三种常用装饰器
- @unittest.skip(reason) :无条件跳过当前测试用例
- @unittest.skipIf(condition,reason) : 当条件表达式的值为true时跳过当前测试用例
- @unittest.skipUnless(condition,reason): 当条件表达式的值为false时跳过当前测试用例
httprunner中的用法
因为httprunner同样也是采用unittest来组织和驱动测试用例执行的,那么使用方法如下。
- skip 无条件的:在测试用例中新增skip字段(skip:"skip this test unconditionally")
skipIf和skipUnless的condition这样的函数表达式调用会稍微麻烦一些,在debug.py中定义了skip_test_in_prodection_env()。 - skifIf 有条件的: 在测试用例中新增 skipIf: ${skip_test_in_production_env()}
- skipUnless 有条件的: 在测试用例集中新增 skipUnless: ${skip_test_in_production_env()}
使用技巧
关于报错
- 报错详情里提示错误详情时,不要首先怀疑源码,debug查看详情后再做进一步判断(hrun xxx.json --log-level debug)
- 如果想保留debug level的信息输入为文件,可以两个命令组合使用(hrun xxx.json --log-level debug --log-file log_name.txt)
关于yml和json格式
httprunner网上多用yml格式编辑,如果case做成时json脚本执行不通过的情况,可以采用如下方式转换为正确的json类型。
- 参照规范做成yml格式的脚本并保证执行ok
- 用在线工具将yml转成json格式http://yaml-online-parser.appspot.com/
- 执行json格式脚本
*同时可用--prettify命令来美化json格式
未解决问题
- python操作csv文件
- 结合jenkins使用
- 报告自定义模板
- 性能测试Locusts应用需要时,再研究用法(目前停留在启动locusts服务后web localhost:8089页面css乱)
使用中的一些问题和解决方案
- 在报错Error: test_0000_0000(httprunner.api,TestSequense), TypeError: request() got an unexpected keyword argument 'validate'时
优先check一下脚本层级是否有问题。有的时候虽然用–validate命令check ok,但实际上脚本仍然有问题。
Version History
Ver.1.5.11中的一些变化
- 一个项目只能有一个debugtalk.py文件。运行时,首先定位debugtalk.py,将其所在目录作为项目的根目录
- 在YAML/JSON测试用例中,CSV文件的路径是相对于根目录的路径。如,引用csv参数的测试用例中的路径写法应为${P(testcases/eventInfo.csv)}
那些坑!
坑1
是的,怎么能少了那些坑呢,果然又一次趴坑了。趴坑里一天多,终于解决了!
现象:json csv参数化时,脚本执行一直报错KeyError: 'name'。于是乎一直以为参数传递的方式有问题,其实不是,不是,不是!!!!!
原因:json脚本格式不正确。一定要注意脚本中各关键字之间的层级关系。如name关键字并不在request中;validate也不在request中。
坑2
测试用例分层执行时,如果依然在.../tests/testcases目录下执行,会报api not found的error。
原因:分层测试case之前,需要先加载api和suite的内容
解决方案:分层测试用例执行时,必须先cd到根目录下(testproject), 再用hrun tests/testcases/xxx.json的命令形式来执行。
如,hrun F:/Python3Workspace/testproject/tests/testcases/usersigntest.json
不算坑,但一点小注意
get方法时,request中的参数数据关键字不是json也不是data,而是params。当不明确request中请求的关键字时可以用charles录制api,转成json查看关键字。

浙公网安备 33010602011771号