概要
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查看关键字。