HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份YAML/JSON脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。
官方文档:https://docs.httprunner.org/
HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS、Linux、Windows 系统平台上。笔者使用的是macOS系统,所以后续都是基于macOS系统的学习。
另外,HttpRunner 支持 Python 3.5 及以上的所有版本,虽然 HttpRunner 暂时保留了对 Python 2.7 的兼容支持,但强烈建议使用 Python 3.6 及以上版本。
电脑上还没安装Python的童鞋还请自行查询安装,非常的简单,这里附上python的官方地址:https://www.python.org/
HttpRunner 的稳定版本托管在 PyPI 上,可以使用pip进行安装,非常的便捷。
打开命令行,输入安装命令即可:
二、安装HttpRunner
HttpRunner 的稳定版本托管在 PyPI 上,可以使用pip进行安装,非常的便捷。
打开CMD,输入安装命令即可:
pip install httprunner
如果已经安装过的,也可以进行更新升级一下。
pip install -U HttpRunner
检验是否安装成功
输入命令:
$ hrun -V
3.1.4
显示出版本号,说明安装成功。
你也可以通过输入:hrun -h
,查看命令帮助说明。
在 HttpRunner 安装成功后,系统中会新增 4 个命令:
httprunner
: 核心命令hrun
:httprunner 的缩写,功能与 httprunner 完全相同hmake
:httprunner make
的别名,用于将YAML/JSON测试用例转换为pytest文件har2case
:辅助工具,可将标准通用的 HAR 格式(HTTP Archive)转换为YAML/JSON格式的测试用例环境装好了,相信很多童鞋已经迫不及待的想run起来了,但是面对一个陌生的框架又无从下手。没关系,我们可以用脚手架来快速生成一个httprunner项目。
我们不妨先输入httprunner startproject -h
,来看一下命令说明。
chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject -h
usage: httprunner startproject [-h] [project_name]
positional arguments:
project_name Specify new project name.
optional arguments:
-h, --help show this help message and exit
可以看出,只需要在命令后面带上项目名称这个参数就好了,那就先来创建一个项目,名称叫httprunner_demo。
chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject httprunner_demo
2021-05-04 17:27:16.152 | INFO | httprunner.scaffold:create_scaffold:43 - Create new project: httprunner_demo
Project Root Dir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
created folder: httprunner_demo
created folder: httprunner_demo/har
created folder: httprunner_demo/testcases
created folder: httprunner_demo/reports
created file: httprunner_demo/testcases/demo_testcase_request.yml
created file: httprunner_demo/testcases/demo_testcase_ref.yml
created file: httprunner_demo/debugtalk.py
created file: httprunner_demo/.env
created file: httprunner_demo/.gitignore
$ tree httprunner_demo -a
2021-05-04 17:27:16.161 | WARNING | httprunner.scaffold:show_tree:29 - tree command not exists, ignore.
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
chenshiengdeMBP:PythonCode chenshifeng$
项目生成完毕,也是非常的简单。
如果你输入的项目名称已经存在,httprunner会给出warning提示。
chenshiengdeMBP:PythonCode chenshifeng$ httprunner startproject httprunner_demo
2021-05-04 17:29:00.631 | WARNING | httprunner.scaffold:create_scaffold:32 - Project folder httprunner_demo exists, please specify a new project name.
$ tree httprunner_demo -a
2021-05-04 17:29:00.636 | WARNING | httprunner.scaffold:show_tree:29 - tree command not exists, ignore.
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
相信了解过django的童鞋能感觉到,httprunner startproject这个命令跟django里的django-admin.py startproject project_name 很像,没错,其实httprunner的想法正式来源于django,这就是httprunner作为一个优秀开源技术资源整合和复用的体现之一,后续还有很多,届时提点出来。
我把生成出的项目用pycharm打开,可以看的生成的目录结构如下图,那么这些都是什么意思呢?
运行用例:hrun httprunner_demo
chenshiengdeMBP:PythonCode chenshifeng$ hrun httprunner_demo
2021-05-04 17:38:15.284 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
2021-05-04 17:38:15.290 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.293 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-04 17:38:15.294 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-04 17:38:15.294 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-04 17:38:15.296 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref.yml
2021-05-04 17:38:15.306 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.306 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-04 17:38:15.307 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-04 17:38:15.308 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
2021-05-04 17:38:15.317 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:38:15.317 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-04 17:38:15.317 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
All done! ✨ ✨
2 files reformatted.
2021-05-04 17:38:16.063 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================= test session starts ==============================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 2 items
httprunner_demo/testcases/demo_testcase_ref_test.py . [ 50%]
httprunner_demo/testcases/demo_testcase_request_test.py . [100%]
============================================== 2 passed in 6.21s ===============================================
可以看的httprunner输出了运行过程中的调试信息。最后,运行结束,2个用例运行pass。
在正式手动编写case之前,我们可以先来熟悉下httprunner的录制生成用例功能。
用postman的童鞋都知道,里面有个功能可以将接口转换成代码,可以直接copy过来使用,提升case编写效率。
那httprunner的录制生成用例功能又是怎么回事呢?
其实,这都要依托于另一个独立的项目-har2case。
原理就是当前主流的抓包工具和浏览器都支持将抓取得到的数据包导出为标准通用的 HAR 格式(HTTP Archive),然后 HttpRunner 将 HAR 格式的数据包转换为YAML/JSON格式的测试用例文件。
比如,我现在用macOS系统上的Charles去抓取一个百度首页搜索httprunner的请求。
运行命令将har文件转换成测试用例:
har2case baidu_home.har
chenshifengdeMacBook-Pro:har chenshifeng$ har2case baidu_home.har
2021-05-04 17:52:23.608 | INFO | httprunner.ext.har2case.core:gen_testcase:356 - Start to generate testcase from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home.har
2021-05-04 17:52:23.608 | INFO | httprunner.ext.har2case.core:_make_testcase:347 - Extract info from HAR file and prepare for testcase.
2021-05-04 17:52:23.611 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-04 17:52:23.611 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-04 17:52:23.612 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-04 17:52:23.612 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-04 17:52:23.612 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home.har
2021-05-04 17:52:23.613 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
2021-05-04 17:52:23.613 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
All done! ✨ ✨
1 file reformatted.
2021-05-04 17:52:23.914 | INFO | httprunner.ext.har2case.core:gen_testcase:377 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/har/baidu_home_test.py
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
生成完毕,在har目录下可以看到生成出的python文件:
# NOTE: Generated By HttpRunner v3.1.4
# FROM: har/baidu_home.har
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseBaiduHome(HttpRunner):
config = Config("testcase description").verify(False)
teststeps = [
Step(
RunRequest("/s")
.get("https://www.baidu.com/s")
.with_params(
**{
"ie": "utf-8",
"mod": "1",
"isbd": "1",
"isid": "C9FF25725AB54698",
"f": "8",
"rsv_bp": "1",
"rsv_idx": "1",
"tn": "baidu",
"wd": "httprunner",
"fenlei": "256",
"oq": "httprunner%20%26lt%3B",
"rsv_pq": "86a39119000039fe",
"rsv_t": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus",
"rqlang": "cn",
"rsv_enter": "0",
"rsv_dl": "tb",
"rsv_sug3": "2",
"rsv_sug1": "2",
"rsv_sug7": "000",
"rsv_btype": "t",
"prefixsug": "httprunner",
"rsp": "1",
"inputT": "6648",
"rsv_sug4": "7252",
"rsv_sug": "2",
"bs": "httprunner 3",
"rsv_sid": "undefined",
"_ss": "1",
"clist": "",
"hsug": "httprunner 3\thttprunner",
"f4s": "1",
"csor": "10",
"_cr1": "40730",
}
)
.with_headers(
**{
"Host": "www.baidu.com",
"Connection": "keep-alive",
"Accept": "*/*",
"is_xhr": "1",
"X-Requested-With": "XMLHttpRequest",
"is_referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cookie": "BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996; H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus; delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937",
}
)
.with_cookies(
**{
"BIDUPSID": "EA49B0E234E0F93BBD3C0082A586CDEA",
"PSTM": "1619952293",
"BAIDUID": "C9FF25B24E5A3C59C96D61DB506725AB:FG=1",
"BD_UPN": "123253",
"BDORZ": "B490B5EBF6F3CD402E515D22BCDA1598",
"H_PS_PSSID": "33986_33819_33849_33759_33675_33607_26350_33996",
"H_PS_645EC": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus",
"delPer": "0",
"BD_CK_SAM": "1",
"PSINO": "5",
"BDSVRTM": "14",
"WWW_ST": "1620121549937",
}
)
.validate()
.assert_equal("status_code", 200)
.assert_equal('headers."Content-Type"', "text/html;charset=utf-8")
),
]
if __name__ == "__main__":
TestCaseBaiduHome().test_start()
因为httprunner封装了pytest,所有既可以用hrun去运行,也可以用pytest去运行。
hrun
很简单,只要在命令后面多加对应的参数就行了。`-2y/--to-yml`或者 `-2j/--to-json`
转为YAML:
har2case baidu_home.har -2y
config:
name: testcase description
variables: {}
verify: false
teststeps:
- name: /s
request:
cookies:
BAIDUID: C9FF25B24E5A3C59C96D61DB506725AB:FG=1
BDORZ: B490B5EBF6F3CD402E515D22BCDA1598
BDSVRTM: '14'
BD_CK_SAM: '1'
BD_UPN: '123253'
BIDUPSID: EA49B0E234E0F93BBD3C0082A586CDEA
H_PS_645EC: 9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus
H_PS_PSSID: '33986_33819_33849_33759_33675_33607_26350_33996'
PSINO: '5'
PSTM: '1619952293'
WWW_ST: '1620121549937'
delPer: '0'
headers:
Accept: '*/*'
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Cookie: BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1;
BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996;
H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus;
delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937
Host: www.baidu.com
Referer: https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
X-Requested-With: XMLHttpRequest
is_referer: https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn
is_xhr: '1'
method: GET
params:
_cr1: '40730'
_ss: '1'
bs: httprunner 3
clist: ''
csor: '10'
f: '8'
f4s: '1'
fenlei: '256'
hsug: "httprunner 3\thttprunner"
ie: utf-8
inputT: '6648'
isbd: '1'
isid: C9FF25725AB54698
mod: '1'
oq: httprunner%20%26lt%3B
prefixsug: httprunner
rqlang: cn
rsp: '1'
rsv_bp: '1'
rsv_btype: t
rsv_dl: tb
rsv_enter: '0'
rsv_idx: '1'
rsv_pq: 86a39119000039fe
rsv_sid: undefined
rsv_sug: '2'
rsv_sug1: '2'
rsv_sug3: '2'
rsv_sug4: '7252'
rsv_sug7: '000'
rsv_t: 9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus
tn: baidu
wd: httprunner
url: https://www.baidu.com/s
validate:
- eq:
- status_code
- 200
- eq:
- headers.Content-Type
- text/html;charset=utf-8
转换为JSON:
har2case baidu_home.har -2j
{
"config": {
"name": "testcase description",
"variables": {},
"verify": false
},
"teststeps": [
{
"name": "/s",
"request": {
"url": "https://www.baidu.com/s",
"params": {
"ie": "utf-8",
"mod": "1",
"isbd": "1",
"isid": "C9FF25725AB54698",
"f": "8",
"rsv_bp": "1",
"rsv_idx": "1",
"tn": "baidu",
"wd": "httprunner",
"fenlei": "256",
"oq": "httprunner%20%26lt%3B",
"rsv_pq": "86a39119000039fe",
"rsv_t": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ++oE9TlK6y2o+O7A7XdDS6Yus",
"rqlang": "cn",
"rsv_enter": "0",
"rsv_dl": "tb",
"rsv_sug3": "2",
"rsv_sug1": "2",
"rsv_sug7": "000",
"rsv_btype": "t",
"prefixsug": "httprunner",
"rsp": "1",
"inputT": "6648",
"rsv_sug4": "7252",
"rsv_sug": "2",
"bs": "httprunner 3",
"rsv_sid": "undefined",
"_ss": "1",
"clist": "",
"hsug": "httprunner 3\thttprunner",
"f4s": "1",
"csor": "10",
"_cr1": "40730"
},
"method": "GET",
"cookies": {
"BIDUPSID": "EA49B0E234E0F93BBD3C0082A586CDEA",
"PSTM": "1619952293",
"BAIDUID": "C9FF25B24E5A3C59C96D61DB506725AB:FG=1",
"BD_UPN": "123253",
"BDORZ": "B490B5EBF6F3CD402E515D22BCDA1598",
"H_PS_PSSID": "33986_33819_33849_33759_33675_33607_26350_33996",
"H_PS_645EC": "9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus",
"delPer": "0",
"BD_CK_SAM": "1",
"PSINO": "5",
"BDSVRTM": "14",
"WWW_ST": "1620121549937"
},
"headers": {
"Host": "www.baidu.com",
"Connection": "keep-alive",
"Accept": "*/*",
"is_xhr": "1",
"X-Requested-With": "XMLHttpRequest",
"is_referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner%203&fenlei=256&oq=httprunner%203&rsv_pq=86a39119000039fe&rsv_t=2b6c1PBdGIcDYzEKyW9BkMzeCPMYcfbqTSf%2FEDXZuefGUrmcy2q1pxhJ0NQ&rqlang=cn",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
"Referer": "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=httprunner&fenlei=256&oq=httprunner%2520%2526lt%253B&rsv_pq=86a39119000039fe&rsv_t=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_sug3=2&rsv_sug1=2&rsv_sug7=000&rsv_btype=t&prefixsug=httprunner&rsp=1&inputT=6648&rsv_sug4=7252&rsv_sug=2",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cookie": "BIDUPSID=EA49B0E234E0F93BBD3C0082A586CDEA; PSTM=1619952293; BAIDUID=C9FF25B24E5A3C59C96D61DB506725AB:FG=1; BD_UPN=123253; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33986_33819_33849_33759_33675_33607_26350_33996; H_PS_645EC=9d65Kx91ldJ2V3LDLjZmstZTQ27dNOYAMJ%2B%2BoE9TlK6y2o%2BO7A7XdDS6Yus; delPer=0; BD_CK_SAM=1; PSINO=5; BDSVRTM=14; WWW_ST=1620121549937"
}
},
"validate": [
{
"eq": [
"status_code",
200
]
},
{
"eq": [
"headers.Content-Type",
"text/html;charset=utf-8"
]
}
]
}
]
}
以上转换出的pytest、yaml、json这3种格式的文件效果都是一样的,用hrun都可以运行,但是用pytest执行的话只可以运行.py的文件了。
上篇文章我们知道了,httprunner可以支持三种格式的用例,分别是pytest、yaml和json。yaml和json是以前的版本所使用的用例格式,但是在3.x版本上,官方强烈建议使用的是pytest格式的用例。
录制生成的case很便捷,但是这并不是说,不需要我们做任何的改动了。在实践的过程中,我们仍然会根据我们实际项目的不同需求来对case作进一步的调整,所以彻底的了解case的构造尤为重要。
首先,我录制了一个百度搜索“httprunner”的一个请求,转换成pytest文件后如下:
可以看到:
补一个官方完整的一个demo代码,并说说httprunner中的用例与我自己编写的测试用例之间的联系。
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner):
config = (
Config("request methods testcase with functions")
.variables(
**{
"foo1": "config_bar1",
"foo2": "config_bar2",
"expect_foo1": "config_bar1",
"expect_foo2": "config_bar2",
}
)
.base_url("http://demo.qa.com")
.verify(False)
.export(*["foo3"])
)
teststeps = [
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "bar11")
.assert_equal("body.args.sum_v", "3")
.assert_equal("body.args.foo2", "bar21")
),
Step(
RunRequest("post form data")
.with_variables(**{"foo2": "bar23"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "$expect_foo1")
.assert_equal("body.form.foo2", "bar23")
.assert_equal("body.form.foo3", "bar21")
),
]
if __name__ == "__main__":
TestCaseRequestWithFunctions().test_start()
我们再一起来看,上面官方给出的例子
即用例名称,这是一个必填参数。测试用例名称,将显示在执行日志和测试报告中。比如,我在之前的百度搜索的case里,加入name。
其实这个配置一般在多环境切换中最常用。
比如你的这套测试用例在qa环境,uat环境都要使用,那么就可以把基础地址(举例http://demo.qa.com),设置进去。在后面的teststep中,只需要填上接口的相对路径就好了(举例 /get)。
这样的话,切换环境运行,只需要修改base_url即可。
变量,这里可以存放一些公共的变量,可以在测试用例里引用。这里大家可以记住这个“公共”的词眼,因为在后面的Step中,还会有步骤变量。
比如说,我的接口有个传参是不变的,比如用户名username,而且后面的每个Step都会用到这个传参,那么username就可以放在config的公共变量里。
另外,Step里的变量优先级是比config里的变量要高的,如果有2个同名的变量的话,那么引用的时候,是优先引用步骤里的变量的。
用来决定是否验证服务器TLS证书的开关。
通常设置为False,当请求https请求时,就会跳过验证。如果你运行时候发现抛错SSLError,可以检查一下是不是verify没传,或者设置了True。
导出的变量,主要是用于Step之间参数的传递。还是以上面的官方代码为例:
一个testcase里(就是一个pytest格式的Python文件)可以有一个或者多个测试步骤,就是teststeps[]列表里的Step。
我的理解每一个Step就可以类比成pytest框架下的def test_xxx()的用例函数,在Step里通常都会要请求API完成测试,也可以调用其他测试用例来完成更多的需求。
可以来看下官方的测试用例逻辑图(2.x版本不同,3.x弃用了2.x的API概念):
可能看起来有点绕,其实官方想表达的就是测试用例分层的一个思想:
测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
测试用例是测试步骤(teststep)的有序集合
测试用例集(testsuite)是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
其实这一点,在我们自己使用pytest框架编写测试用例的时候同样贯彻到了。为了自动化测试的稳定性和可维护性,每个测试用例之间相互独立是非常有必要的。
先上一段Step的代码,结合下面的点对照着看:
teststeps = [
Step(
RunRequest("get with params")
.with_variables(
**{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"}
)
.get("/get")
.with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
.with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"})
.extract()
.with_jmespath("body.args.foo2", "foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.args.foo1", "bar11")
.assert_equal("body.args.sum_v", "3")
.assert_equal("body.args.foo2", "bar21")
),
从上面的代码可以看出,RunRequest的作用就是在测试步骤中请求API,并且可以对于API的响应结果进行提取、断言。
1.RunRequest(name)
RunRequest的参数名用于指定teststep名称,它将显示在执行日志和测试报告中。
另外,如果config和Step里有重名的变量,那么当你引用这个变量的时候,Step变量会覆盖config变量。
这里要注意的是,如果在config里设置了基础url,那么步骤里的url就只能设置相对路径了。
这里是采用了JMESPath语言,JMESPath是JSON的查询语言,可以便捷的提取json中你需要的元素。
第一个参数是你的目标元素的jmespath表达式,第二个元素则是用来存放这个元素的变量,供有需要的引用。
这里不展开,后面单讲。
那在httprunner框架中,可以使用assert_XXX(jmes_path: Text, expected_value: Any)来进行提取和验证。
第一个参数还是jmespath表达式,第二个参数则是预期值。
assert_XXX这种方式相信用过自动化测试框架的都不会陌生,所以也非常容易上手。目前httprunner还是封装了非常丰富的断言方法的,相信可以满足绝大多数的需求了。
以前我在写接口自动化用例的时候,为了保证用例的独立性,需要在setUp里调用各种满足用例的一些前置条件,其中就不乏调用了其他测试用例中的方法。
而httprunner也是支持了这一项很重要的特性,通过RunTestCase对其他测试用例进行调用,并且还可以导出用例中你所需要的变量,来满足后续用例的的运行。
首先还是来看下RunTestCase的用法,然后再用实例去实践。下面是官方的一个演示代码。
import os
import sys
sys.path.insert(0, os.getcwd())
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from examples.postman_echo.request_methods.request_with_functions_test import (
TestCaseRequestWithFunctions as RequestWithFunctions,
)
class TestCaseRequestWithTestcaseReference(HttpRunner):
config = (
Config("request methods testcase: reference testcase")
.variables(
**{
"foo1": "testsuite_config_bar1",
"expect_foo1": "testsuite_config_bar1",
"expect_foo2": "config_bar2",
}
)
.base_url("https://postman-echo.com")
.verify(False)
)
teststeps = [
Step(
RunTestCase("request with functions")
.with_variables(
**{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"}
)
.call(RequestWithFunctions)
.export(*["foo3"])
),
Step(
RunRequest("post form data")
.with_variables(**{"foo1": "bar1"})
.post("/post")
.with_headers(
**{
"User-Agent": "HttpRunner/${get_httprunner_version()}",
"Content-Type": "application/x-www-form-urlencoded",
}
)
.with_data("foo1=$foo1&foo2=$foo3")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.form.foo1", "bar1")
.assert_equal("body.form.foo2", "bar21")
),
]
if __name__ == "__main__":
TestCaseRequestWithTestcaseReference().test_start()
这里需要特别说明下,导入的testcase需要起个别名,不能已test开头,会被系统识别成一个testcase
看到这里,对于httprunner已经有了一个大概的了解,现在想对于一些比较重要或者常用的功能,进行一些实践操作。
毕竟那谁说过,“纸上得来终觉浅,绝知此事要躬行。”
上一篇提到了RunTestCase,里面有2个重要的特征:
一个是在一个用例中引用另一个测试用例,另一个则是变量的导出与引用。
那就先来实践一下这2个货。
我用flask快速写了2个接口,以供在本地调用:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@test_flask_demo.py
@time:2021/03/12
"""
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/getUserName', methods=['GET'])
def get_user_name():
if request.method == 'GET':
return {
"username": "chenshifeng",
"age": "18",
"from": "China",
}
@app.route('/joinStr', methods=['GET'])
def str_join():
if request.method == 'GET':
str1 = request.args.get("str1")
str2 = request.args.get("str2")
after_join = str1 + " " + str2
return {
"result": after_join
}
if __name__ == '__main__':
app.run()
一共有2个接口:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:test_getUserName_demo.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithGetUserName(HttpRunner):
config = (
Config("test /getUserName")
.base_url("http://localhost:5000")
.verify(False)
)
teststeps = [
Step(
RunRequest("getUserName")
.get("/getUserName")
.validate()
.assert_equal("body.username", "chenshifeng")
),
]
if __name__ == "__main__":
TestCaseRequestWithGetUserName().test_start()
这里呢,步骤都有了,断言是验证返回的username字段值是不是“chenshifeng”,运行一下,可以看到测试通过。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:joinStr_demo_test.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithJoinStr(HttpRunner):
config = (
Config("test /joinStr")
.base_url("http://localhost:5000")
.verify(False)
)
teststeps = [
Step(
RunRequest("joinStr")
.get("/joinStr")
.with_params(**{"str1": "hello", "str2": "chenshifeng"})
.validate()
.assert_equal("body.result", "hello chenshifeng")
),
]
if __name__ == "__main__":
TestCaseRequestWithJoinStr().test_start()
这里传入的参数分别是“hello”、“chenshifeng”,这个字符串在拼接的时候是加了一个空格的,所以断言的时候我预期的值是"hello chenshifeng"。
运行测试,可以看到测试通过。
假设,/joinStr接口的第二个参数,是依赖/getUserName接口的返回,那么现在这2个testcase之间就有了依赖关系。
那么在写/getUserName接口用例的时候,就需要去引用/joinStr的测试用例了,并且需要把/getUserName用例的变量导出来,/joinStr的测试用例传参时候使用。
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithGetUserName(HttpRunner):
config = (
Config("test /getUserName")
.base_url("http://localhost:5000")
.verify(False)
.export(*["username"]) # 这里定义出要导出的变量
)
teststeps = [
Step(
RunRequest("getUserName")
.get("/getUserName")
.extract()
.with_jmespath("body.username", "username") # 提取出目标值,赋值给username变量
.validate()
.assert_equal("body.username", "chenshifeng")
),
]
if __name__ == "__main__":
TestCaseRequestWithGetUserName().test_start()
关注注释部分的代码,一个是config里定义了这个要导出的变量,另一个是在Step中,讲目标值提取出来,赋值给这个变量。
2. 接下来,修改/joinStr接口的测试用例:
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from testcases.test_getUserName_demo import TestCaseRequestWithGetUserName as RequestWithGetUserName # 记得要导入引用的类
class TestCaseRequestWithJoinStr(HttpRunner):
config = (
Config("test /joinStr")
.base_url("http://localhost:5000")
.verify(False)
)
teststeps = [
Step(
RunTestCase("setUp getUserName")
.call(RequestWithGetUserName) # 导入后就可以调用了
.export(*["username"]) # 在RunTestCase步骤中定义这个变量的导出
),
Step(
RunRequest("joinStr")
.get("/joinStr")
.with_params(**{"str1": "hello", "str2": "$username"}) # 在第二个传参中引用导出的变量
.validate()
.assert_equal("body.result", "hello $username") # 断言的预期值也引用变量
),
]
if __name__ == "__main__":
TestCaseRequestWithJoinStr().test_start()
按照直接学习的内容,case已经修改好,现在运行/joinStr接口的测试用例,可以看到运行通过。
在之前的demo过程中,已经运行过testcase了,那这篇就也来汇总一下,运行case相关的知识点。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author:chenshifeng
@file:joinStr_demo_test.py
@time:2021/05/05
"""
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
from testcases.test_getUserName_demo import TestCaseRequestWithGetUserName as RequestWithGetUserName # 记得要导入引用的类
class TestCaseRequestWithJoinStr(HttpRunner):
config = (
Config("test /joinStr")
.base_url("http://localhost:5000")
.verify(False)
)
teststeps = [
Step(
RunTestCase("setUp getUserName")
.call(RequestWithGetUserName) # 导入后就可以调用了
.export(*["username"]) # 在RunTestCase步骤中定义这个变量的导出
),
Step(
RunRequest("joinStr")
.get("/joinStr")
.with_params(**{"str1": "hello", "str2": "$username"}) # 在第二个传参中引用导出的变量
.validate()
.assert_equal("body.result", "hello $username") # 断言的预期值也引用变量
),
]
if __name__ == "__main__":
TestCaseRequestWithJoinStr().test_start() # 这里
main方法里在类的后面调用test_start()方法即可。
命令行的话,就是直接在hrun后面加上case的路径,就可以运行了。
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/demo_testcase_request_test.py
2021-05-05 01:04:33.386 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-05 01:04:33.387 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do
2021-05-05 01:04:33.581 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 1 item
testcases/demo_testcase_request_test.py . [100%]
============================================================================== 1 passed in 2.28s ==============================================================================
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/getUserName_demo_test.py
2021-05-05 01:05:13.318 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/getUserName_demo_test.py
2021-05-05 01:05:13.318 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do
2021-05-05 01:05:13.510 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 1 item
testcases/getUserName_demo_test.py . [100%]
============================================================================== 1 passed in 0.18s ==============================================================================
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/getUserName_demo_test.py testcases/joinStr_demo_test.py
2021-05-05 01:12:04.043 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/getUserName_demo_test.py
2021-05-05 01:12:04.044 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/joinStr_demo_test.py
2021-05-05 01:12:04.044 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
No Path provided. Nothing to do
2021-05-05 01:12:04.231 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 2 items
testcases/getUserName_demo_test.py . [ 50%]
testcases/joinStr_demo_test.py . [100%]
============================================================================== 2 passed in 0.22s ==============================================================================
Sentry is attempting to send 0 pending error messages
Waiting up to 2 seconds
Press Ctrl-C to quit
chenshifengdeMacBook-Pro:httprunner_demo chenshifeng$ hrun testcases/
2021-05-05 01:13:05.177 | INFO | httprunner.make:__make:512 - make path: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/
2021-05-05 01:13:05.183 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.186 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/.env
2021-05-05 01:13:05.187 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME
2021-05-05 01:13:05.187 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD
2021-05-05 01:13:05.187 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref.yml
2021-05-05 01:13:05.199 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.200 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-05 01:13:05.200 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
2021-05-05 01:13:05.201 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
2021-05-05 01:13:05.211 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2
2021-05-05 01:13:05.213 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request.yml
2021-05-05 01:13:05.213 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ...
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_ref_test.py
reformatted /Users/chenshifeng/MyCode/PythonCode/httprunner_demo/testcases/demo_testcase_request_test.py
All done! ✨ ✨
2 files reformatted.
2021-05-05 01:13:05.687 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4
============================================================================= test session starts =============================================================================
platform darwin -- Python 3.9.2, pytest-6.2.2, py-1.10.0, pluggy-0.13.1
rootdir: /Users/chenshifeng/MyCode/PythonCode/httprunner_demo
plugins: metadata-1.11.0, html-2.1.1, allure-pytest-2.8.40
collected 4 items
testcases/demo_testcase_ref_test.py . [ 25%]
testcases/joinStr_demo_test.py . [ 50%]
testcases/demo_testcase_request_test.py . [ 75%]
testcases/getUserName_demo_test.py . [100%]
============================================================================== 4 passed in 3.93s ==============================================================================
如果运行YAML/JSON文件,同级文件夹下,就会生成对应的pytest的文件,文件名称的末尾会有_test。其实httprunner会先把它们转换为pytest格式的,再去运行。
所以,用httprunner 3.x版本的话,写case的话还是直接用pytest格式吧。
对应pytest文件来说,用hrun或者pytest命令运行都是可以的。
因为hrun只是封装了pytest,所以pytest运行的所有参数,同样可以在hrun后面加。