1. 工欲善其事,必先利其器
作为一个测试从事人员,
你是否为每天“点点点”的工作而感到索然无味,提不起丝毫兴趣?
你是否因为没有强大的代码编写能力,对有难度的测试任务望而却步?
每天重复的功能测试,日复一日的“原地踏步”,你,感到焦虑吗?
好像HttpRunner
可以解决上述问题.
据HttpRunner
作者介绍该工具遵循 约定大于配置 的准则.
不追求重复造轮子,而是将强大的轮子组装成战车
追求投入产出比,一份投入即可实现多种测试需求
通过配置文件的方式描述用例.
如此一来,简单的测试用例并不需要有较强的代码编写能力.
只需要依葫芦画瓢,修改若干配置,将所需用例描述出来即可.
2. 磨刀不误砍柴工
效率,考量一件工具是否好用的标准.
在使用之前, 我们先来分析一下该工具能好用么?
HttpRunner
作者反复的考量设计模式. 给出了适合并优雅的设计.
抽象测试工作组件(HttpRunner文档原图):

- 测试用例 : 测试用例为最小组件, 测试用例 = 测试脚本 + 测试数据
- 测试脚本 : 重点是描述测试的 业务功能逻辑,包括预置条件、测试步骤、预期结果等,并且可以结合辅助函数(debugtalk.py)实现复杂的运算逻辑;可以将 测试脚本 理解为编程语言中的 类(class)
- 测试数据 : 重点是对应测试的 业务数据逻辑,可以理解为类的实例化数据;
- 测试步骤 : 测试用例是测试步骤的 "有序" 集合 而对于接口测试来说,每一个测试步骤应该就对应一个 API 的请求描述。
- 测试用例集 : 测试用例集 是 测试用例 的 "无序" 集合 集合中的测试用例应该都是相互独立,不存在先后依赖关系的。 如果确实存在先后依赖关系怎么办,例如登录功能和下单功能。 正确的做法应该是,在下单测试用例的前置步骤中执行登录操作。
有了以上的思路,就不难想象出应该以何种正确的姿势使用工具了.
3. 擒龙要下海,打虎要上山
咱们动手试试这个工具是否真的很厉害.
首先HttpRunner
是支持测试接口录制的.
快速上手用法很简单
- 打开代理工具Charles
- 操作一遍测试的接口用例
- 导出.har格式抓包结果
- 通过工具自带命令har2case导出成yml配置文件
- 通过命令工具hrun 执行yml配置文件
如此就重放了一遍刚刚操作的测试接口用例. 就是这么简单!
进阶用法,自定义一个测试用例.
使用官方的脚手架工具可以生产一套示例代码.
目录结构(官方推荐)如下
hrun --startproject demo
Start to create new project: demo
CWD: /Users/fangzimo/Downloads/test
created folder: demo
created folder: demo/api
created folder: demo/testcases
created folder: demo/testsuites
created folder: demo/reports
created file: demo/api/demo_api.yml
created file: demo/testcases/demo_testcase.yml
created file: demo/testsuites/demo_testsuite.yml
created file: demo/debugtalk.py
created file: demo/.env
created file: demo/.gitignore
#################################################
tree -a demo
demo
├── .env
├── .gitignore
├── api
│ └── demo_api.yml
├── debugtalk.py
├── reports
├── testcases
│ └── demo_testcase.yml
└── testsuites
└── demo_testsuite.yml
说一下目录结构:
- .env 放置在项目根目下,一般将敏感 公用信息存放与此
- debugtalk.py 封装例中用到的一些自定义处理逻辑
- 接口定义(API)描述api调用基础信息(如同api文档一般的基础信息),地址,入参,返回
- 测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
- 测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立
- 若有存储参数化文件,或者项目依赖的文件,可以新建并放到 data 文件夹
- reports 存储 HTML 测试报告
将上面的demo简单用起来:
- 定义一个实际的api
- 针对这个api创建2个测试用例
- 定义一个测试用例集包含这两个测试用例.
- 用数据驱动的方式跑这个测试用例集
借用一下百度翻译的接口: https://fanyi.baidu.com/sug 这个接口需要post方式传递一个参数kw 然后会返回输入建议信息
curl "https://fanyi.baidu.com/sug" -d "kw=test"
{
"errno": 0,
"data": [
{
"k": "test",
"v": "n. 测验; 考查; (医疗上的)检查,化验,检验; 试验; 测试; v. 测验; 考查; 试验; "
},
{
"k": "tests",
"v": "n. 测验; 考查; (医疗上的)检查,化验,检验; 试验; 测试; v. 测验; 考查; 试验; "
},
{
"k": "testimony",
"v": "n. 证据; 证明; 证词; 证言; 口供;"
},
{
"k": "testing",
"v": "n. 试验; 测试; 检查; adj. 棘手的; 伤脑筋的; 难应付的; v. 测验; 考查; 试验"
},
{
"k": "tested",
"v": "v. 测验; 考查; 试验; 检查; 化验; 检验; 测试; test的过去分词和过去式;"
}
]
}
在.env
中定义出接口的公用信息:
BASE_URL=https://fanyi.baidu.com
在 api 创建 sug.yml 定义api
name: sug api
variables:
key_word: "" #定义变量
request:
url: /sug #路径
method: POST
headers: #请求头信息
Content-Type: "application/x-www-form-urlencoded; charset=UTF-8"
data:
kw: $key_word #通过变量赋值
validate: #返回校验
- eq: ["status_code", 200]
创建一个teststep
,在 testcases
中新建 sug_step.yml
config:
name: "get sug info"
id: sug_info
variables:
key_word: test
base_url: ${ENV(BASE_URL)}
verify: False
export:
- data
teststeps:
- name: sug info
api: api/sug.yml
variables:
key_word: $key_word
extract:
- data: content.data
创建两个测试用例,一个测试成功状态 sug_info_success.yml
,一个测试失败状态 sug_info_fail.yml
sug_info_success.yml
config:
name: "get sug info success"
id: sug_info_succ
base_url: ${ENV(BASE_URL)}
variables:
key_word: test
teststeps:
- name: get sug info
testcase: testcases/sug_step.yml
extract:
- data
validate:
- eq: ["status_code", 200]
- eq: ["content.errno", 0]
sug_info_fail.yml
config:
name: "get sug info fail"
id: sug_info_fail
base_url: ${ENV(BASE_URL)}
variables:
key_word: ""
teststeps:
- name: get sug info
testcase: testcases/sug_step.yml
extract:
- data
validate:
- eq: ["status_code", 200]
- eq: ["content.errno", 1]
最后在 testsuites
中创建一个测试用例集把上面两个用例包含进来. 并且采用数据驱动的方式跑测试成功用例.注意定义方式,本示例中是通过函数方式返回.
config:
name: "sug info testsuite"
variables:
key_word: ""
base_url: ${ENV(BASE_URL)}
testcases:
- name: call sug info success with data
testcase: testcases/sug_info_success.yml
variables:
key_word: $key_word
parameters:
key_word: ${get_key_word(1)}
- name: call sug info fail
testcase: testcases/sug_info_fail.yml
variables:
key_word: ""
在debugtalk.py
中完善 get_key_word
方法 简单演示了函数的使用,和传参方式,以及数据的返回.
def get_key_word(index):
words = {
1: ['a', 'b', 'c', 'd'],
2: ['e', 'f', 'g', 'h']
}
return words.get(index)
最后激动人心的时刻到了!执行用例~
先来试试完整且独立的最小单元case
➜ demo hrun testcases/sug_info_success.yml
INFO HttpRunner version: 2.2.5
INFO Loading environment variables from /Users/fangzimo/Downloads/test/demo/.env
INFO Start to run testcase: get sug info success
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 187.6 ms, response_length: 889 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : [{"k": "test", "v": "n. \u6d4b\u9a8c; \u8003\u67e5; (\u533b\u7597\u4e0a\u7684)\u68c0\u67e5\uff0c\u5316\u9a8c\uff0c\u68c0\u9a8c; \u8bd5\u9a8c; \u6d4b\u8bd5; v. \u6d4b\u9a8c; \u8003\u67e5; \u8bd5\u9a8c; "}, {"k": "tests", "v": "n. \u6d4b\u9a8c; \u8003\u67e5; (\u533b\u7597\u4e0a\u7684)\u68c0\u67e5\uff
0c\u5316\u9a8c\uff0c\u68c0\u9a8c; \u8bd5\u9a8c; \u6d4b\u8bd5; v. \u6d4b\u9a8c; \u8003\u67e5; \u8bd5\u9a8c; "}, {"k": "testimony", "v": "n. \u8bc1\u636e; \u8bc1\u660e; \u8bc1\u8bcd; \u8bc1\u8a00; \u53e3\u4f9b;"}, {"k": "testing", "v": "n. \u8bd5\u9a8c; \u6d4b\u8bd5; \u68c0\u67e5; adj. \u68d8\u624b\u7684; \u4f24\u8111\
u7b4b\u7684; \u96be\u5e94\u4ed8\u7684; v. \u6d4b\u9a8c; \u8003\u67e5; \u8bd5\u9a8c"}, {"k": "tested", "v": "v. \u6d4b\u9a8c; \u8003\u67e5; \u8bd5\u9a8c; \u68c0\u67e5; \u5316\u9a8c; \u68c0\u9a8c; \u6d4b\u8bd5; test\u7684\u8fc7\u53bb\u5206\u8bcd\u548c\u8fc7\u53bb\u5f0f;"}]
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.189s
OK
INFO Start to render Html report ...
INFO Generated Html report: /Users/fangzimo/Downloads/test/demo/reports/1569234066.html
➜ demo hrun testcases/sug_info_fail.yml
INFO HttpRunner version: 2.2.5
INFO Loading environment variables from /Users/fangzimo/Downloads/test/demo/.env
INFO Start to run testcase: get sug info fail
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 176.11 ms, response_length: 21 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : []
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.177s
OK
INFO Start to render Html report ...
INFO Generated Html report: /Users/fangzimo/Downloads/test/demo/reports/1569234134.html
测试报告就在reports目录下.可以查看具体的详情(请求信息,响应信息等))
最后我们来跑一下这个测试集
➜ demo hrun testsuites/sug_info_testsuite.yml
INFO HttpRunner version: 2.2.5
INFO Loading environment variables from /Users/fangzimo/Downloads/test/demo/.env
INFO Start to run testcase: call sug info success with data
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 158.67 ms, response_length: 846 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : [{"k": "and", "v": "conj. \u548c; \u4e0e; \u540c; \u53c8; \u800c; \u52a0; \u52a0\u4e0a; \u7136\u540e; \u63a5\u7740;"}, {"k": "available", "v": "adj. \u53ef\u83b7\u5f97\u7684; \u53ef\u8d2d\u5f97\u7684; \u53ef\u627e\u5230\u7684; \u6709\u7a7a\u7684;"}, {"k": "as", "v": "prep. \u50cf; \u5982\u540c; \u4f5c\u4e3a; \u5f53\u4f5c; adv. (\u6bd4\u8f83\u65f6\u7528)\u50cf\u2026\u4e00\u6837\uff0c\u5982\u540c; (\u6307\u4e8b\u60c5\u4ee5\u540c\u6837\u7684\u65b9"}, {"k": "all", "v": "det. \u6240\u6709; \u5168\u90e8; \u5168\u4f53; \u4e00\u5207; (\u4e0e\u5355\u6570\u540d\u8bcd\u8fde\u7528\uff0c\u8868\u793a\u67d0\u4e8b\u5728\u67d0\u6bb5\u65f6\u95f4\u5185\u6301\u7eed\u53d1\u751f)\u5168\u90e8\u7684\uff0c\u6574"}, {"k": "at", "v": "prep. \u5728(\u67d0\u5904); \u5728(\u5b66\u4e60\u6216\u5de5\u4f5c\u5730\u70b9); \u5728(\u67d0\u65f6\u95f4\u6216\u65f6\u523b);"}]
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.160s
OK
INFO Start to run testcase: call sug info success with data
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 133.78 ms, response_length: 961 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : [{"k": "bear", "v": "v. \u627f\u53d7; \u5fcd\u53d7; \u4e0d\u9002\u4e8e\u67d0\u4e8b(\u6216\u505a\u67d0\u4e8b); \u627f\u62c5\u8d23\u4efb; n. \u718a; (\u5728\u8bc1\u5238\u5e02\u573a\u7b49)\u5356\u7a7a\u7684\u4eba;"}, {"k": "but", "v": "conj. \u800c; \u76f8\u53cd; \u7136\u800c; \u5c3d\u7ba1\u5982\u6b64; \u8868\u793a\u6b49\u610f\u65f6\u8bf4; prep. \u9664\u4e86; \u9664\u2026\u4e4b\u5916; adv"}, {"k": "break", "v": "v. (\u4f7f)\u7834\uff0c\u88c2\uff0c\u788e; \u5f04\u574f; \u635f\u574f; \u574f\u6389; \u5f04\u7834; \u4f7f\u6d41\u8840; n. \u95f4\u6b47; \u4f11\u606f; \u8bfe\u95f4\u4f11\u606f;"}, {"k": "back", "v": "n. (\u4eba\u4f53\u6216\u52a8\u7269\u7684)\u80cc\u90e8\uff0c\u80cc; \u8170\u80cc; \u810a\u67f1; \u810a\u6881\u9aa8; \u540e\u90e8; \u540e\u9762; \u672b\u5c3e; adj. \u80cc\u540e\u7684"}, {"k": "business", "v": "n. \u5546\u4e1a; \u4e70\u5356; \u751f\u610f; \u5546\u52a1; \u516c\u4e8b; \u8425\u4e1a\u989d; \u8d38\u6613\u989d; \u8425\u4e1a\u72b6\u51b5;"}]
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.135s
OK
INFO Start to run testcase: call sug info success with data
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 233.54 ms, response_length: 1124 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : [{"k": "can", "v": "modal (\u8868\u793a\u6709\u80fd\u529b\u505a\u6216\u80fd\u591f\u53d1\u751f)\u80fd\uff0c\u4f1a; (\u8868\u793a\u77e5\u9053\u5982\u4f55\u505a)\u61c2\u5f97; \u4e0e\u52a8\u8bcdfeel\u3001hear\u3001"}, {"k": "correct", "v": "adj. \u51c6\u786e\u65e0\u8bef\u7684; \u7cbe\u786e\u7684; \u6b63\u786e\u7684; \u6070\u5f53\u7684; \u5408\u9002\u7684; (\u4e3e\u6b62\u8a00\u8c08)\u7b26\u5408\u516c\u8ba4\u51c6\u5219\u7684\uff0c\u5f97\u4f53\u7684;"}, {"k": "certain", "v": "adj. \u786e\u5b9e; \u786e\u5b9a; \u80af\u5b9a; \u786e\u4fe1; \u65e0\u7591; (\u4e0d\u63d0\u53ca\u7ec6\u8282\u65f6\u7528)\u67d0\u4e8b\uff0c\u67d0\u4eba\uff0c\u67d0\u79cd; pron. "}, {"k": "common", "v": "adj. \u5e38\u89c1\u7684; \u901a\u5e38\u7684; \u666e\u904d\u7684; \u5171\u6709\u7684; \u5171\u4eab\u7684; \u5171\u540c\u7684; \u666e\u901a\u7684; \u5e73\u5e38\u7684; \u5bfb\u5e38\u7684; "}, {"k": "course", "v": "n. (\u6709\u5173\u67d0\u5b66\u79d1\u7684\u7cfb\u5217)\u8bfe\u7a0b\uff0c\u8bb2\u5ea7; (\u5927\u5b66\u4e2d\u8981\u8fdb\u884c\u8003\u8bd5\u6216\u53d6\u5f97\u8d44\u683c\u7684)\u8bfe\u7a0b; (\u8239\u6216\u98de\u673a\u7684)\u822a\u5411\uff0c"}]
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.234s
OK
INFO Start to run testcase: call sug info success with data
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 223.75 ms, response_length: 951 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : [{"k": "during", "v": "prep. \u5728\u2026\u671f\u95f4; \u5728\u2026\u671f\u95f4\u7684\u67d0\u4e2a\u65f6\u5019;"}, {"k": "draw", "v": "v. (\u7528\u94c5\u7b14\u3001\u94a2\u7b14\u6216\u7c89\u7b14)\u753b\uff0c\u63cf\u7ed8\uff0c\u63cf\u753b; \u62d6(\u52a8); \u62c9(\u52a8); \u7275\u5f15; \u62c9\uff0c\u62d6(\u8f66); \u5438\u5f15\uff0c"}, {"k": "dress", "v": "n. \u8fde\u8863\u88d9; \u8863\u670d; v. \u7a7f\u8863\u670d; \u7ed9(\u67d0\u4eba)\u7a7f\u8863\u670d; \u7a7f\u2026\u7684\u670d\u88c5; \u7a7f\u6b63\u5f0f\u670d\u88c5;"}, {"k": "do", "v": "v. \u505a\uff0c\u5e72\uff0c\u529e(\u67d0\u4e8b); (\u4ee5\u67d0\u79cd\u65b9\u5f0f)\u505a; \u884c\u52a8; \u8868\u73b0; (\u95ee\u8be2\u6216\u8c08\u8bba\u65f6\u7528)\u8fdb\u5c55\uff0c\u8fdb\u884c; au"}, {"k": "drop", "v": "v. (\u610f\u5916\u5730)\u843d\u4e0b\uff0c\u6389\u4e0b\uff0c\u4f7f\u843d\u4e0b; (\u6545\u610f)\u964d\u4e0b\uff0c\u4f7f\u964d\u843d; \u7d2f\u5012; \u7d2f\u57ae; n. \u6ef4; \u6c34\u73e0; \u5c11"}]
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.225s
OK
INFO Start to run testcase: call sug info fail
get sug info
INFO POST https://fanyi.baidu.com/sug
INFO status_code: 200, response_time(ms): 225.91 ms, response_length: 21 bytes
INFO
==================== Output ====================
Variable : Value
---------------- : -----------------------------
data : []
------------------------------------------------
.
----------------------------------------------------------------------
Ran 1 test in 0.227s
OK
INFO Start to render Html report ...
INFO Generated Html report: /Users/fangzimo/Downloads/test/demo/reports/1569234320.html
没问题,成功,鼓掌!
但好像工作中并没有这么简单. 通常会遇见的问题:
- 接口中的参数需要依赖其他接口返回
- 测试用例需要数据驱动,通常是一组数据
- 需要对接口做类似等待一段时间再请求的控制干预等操作
这些问题是很容易出现的,一般使用的自动化框架,大多基于写代码逻辑实现,灵活度较高. 这些问题相对容易解决.但是基于配置的,灵活度就有很大的牺牲,更严格的定义好了玩法和规则, 之中的取舍可见设计者对测试自动化的理解, 可以看看设计者的博客,有设计的心路历程,对于上述问题,作者最后给出了较为优雅的解决方式.
- 接口中的参数需要依赖其他接口返回
通过配置文件中对变量的支持来实现
extract
提取变量
variables
定义变量 - 测试用例需要数据驱动,通常是一组数据.
参数化数据驱动方式解决
通过配置参数为列表的方式实现
通过文件cvs
的格式配置实现 - 需要对接口做类似等待一段时间再请求的控制干预操作
实现了hooks机制,可以在请求前和请求后调用钩子函数
setup_hooks
在HTTP请求发送前执行hook
函数,主要用于准备等工作
teardown_hooks
在HTTP请求发送后执行hook
函数,主要用户测试后的清理等工作
4. 与Unittest+Request+HTMLRunner框架对比
最后我们来看看这个框架的优缺点,到底适不适合我们呢?
HttpRunner
- 优点
- 基于
YAML/JSON
格式,专注于接口本身的编写 - 接口编写简单,容易上手,对代码编写能力要求较低
- 生成测试报告,可以自动生成测试报告,框架自带的测试报告模板基本满足需求,支持自定义测试报告的模板
- 接口录制功能,操作简单,只需3步即可完成测试,对于较为简单的场景尤其方便
- 分层机制,适合冒烟流程测试,无需重复编写接口,只要根据需求灵活调用即可
- 缺点
- 没有编辑器插件对语法校验,容易出错,
HttpRunner
没有编辑器插件,本身就是一个配置文件,所以只要是合法的YAML/JSON
格式,就算写错了,也看不出来,只有运行起来才知道 - 框架推出时间相对较短,官方文档没有特别详细的说明,且网上资料相对其他主流测试框架较少
- 扩展不方便,数据驱动需要依赖其他接口返回,且有先后顺序,这个比较麻烦,暂时框架不支持很优雅的解决这种情况.可以通过分步来解决这个问题
- 首先数据驱动可以通过测试用例集的定义方式实现
- 调用有序这个问题,就只能通过分拆测试用例,定义两个数据集,然后通过python脚本来控制先后执行顺序
- 由于用例的数据导出只能在一个测试周期中,所以我们还要解决测试数据传递的问题
- 通过写入文件的方式解决.接口返回的测试数据写入文件,然后需要的地方通过读取文件的方式读回数据
这样可以解决这个问题,但是很繁琐,这样的情况多了以后 项目大了,就会变得很凌乱了,和我们的预期也并不相符
Unittest+Request+HTMLRunner
- 优点
- 足够灵活强大!只要你懂Python开发,想怎么玩就怎么玩
- 支持分层测试、数据驱动、测试报告、集成CI等
- 支持自动发送邮件、定时任务等
- 支持读取excel文件的测试数据,能够组织多个用例去执行
- 提供丰富的断言方法等
- 缺点
- 有一定的学习成本,对于代码编写能力要求较高,不易上手
总结来说:
HttpRunner
清晰的配置文件,对于不复杂项目测试需求基本都能满足.实际调试中,错误提示不够明显, 最后在利弊之间权衡, 个人感觉HttpRunner
不太适合大型的逻辑复杂的项目.本来简单的特性反而成了制约的弊端.
HttpRunner
文档写的很详细,实际使用的时候一定要多阅读.作者博客也非常值得拜读,能更加深入的理解和学习作者的设计.