今天,我们来一起学习下HttpRunner3。主要讲解如何使用、应用技巧、基本知识点总结和需要注意事项。一篇文章没法面面俱到,如果有重要的地方没写到,可以给我留言,咱们接着补充。
整体概览
概述介绍
HttpRunner 是一款面向 HTTP(S) 协议的通用测试框架,只需编写维护一份 YAML/JSON 脚本,即可实现自动化测试、性能测试、线上监控、持续集成等多种测试需求。
官方文档
文档中的内容更详细,使用过程遇到问题多来看看吧。
GitHub - httprunner/httprunner: HttpRunner 是一个开源的 API/UI 测试工具,简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
HttpRunner V3.x Docs
大佬翻译的版本:
HttpRunner V3.x中文文档
设计理念
相比于其它 API 测试工具,HttpRunner 最大的不同在于设计理念。
约定大于配置:测试用例是标准结构化的,格式统一,方便协作和维护
充分复用优秀的开源项目,不追求重复造轮子,而是将强大的轮子组装成战车
追求投入产出比,一份投入即可实现多种测试需求
关键特性
- 继承的所有强大功能requests ,只需以人工方式获得乐趣即可处理HTTP(S)。
- 以YAML或JSON格式定义测试用例,pytest 以简洁优雅的方式运行。
- 在HAR 支持下记录并生成测试用例。
- 支持variables/ extract/ validate/hooks机制,以创建非常复杂的测试方案。
- 使用debugtalk.py插件,任何功能都可以在测试用例的任何部分使用。
- 使用jmespath ,提取和验证json响应从未如此简单。
- 有了pytest ,数百个插件随时可用。
- 使用allure ,测试报告可以非常强大。
- 通过重复使用locust ,您可以进行性能测试,而无需进行额外的工作。
- 支持CLI命令,与CI/CD完美结合。
简单的总结下:
-
HttpRunner继承了所有requests的强大功能,可以用YAML或JSON格式编写测试用例,以pytest方式运行。
-
支持HAR 格式的录制文件生成测试用例,支持variables/ extract/ validate/hooks机制,能够创建非常复杂的测试方案。
-
可以借助debugtalk.py强大的插件功能助力编写测试用例,提取和验证json响应使用jmespath 。
核心概念
测试用例(testcase)应该是完整且独立的,每条测试用例应该是都可以独立运行的
测试用例是测试步骤(teststep)的 有序 集合,每一个测试步骤对应一个 API 的请求描述
测试用例集(testsuite)是测试用例的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
测试步骤(teststep)
测试用例是测试步骤的 有序 集合,而对于接口测试来说,每一个测试步骤应该就对应一个 API 的请求描述。
测试用例(TestCase)
从 2.0 版本开始,HttpRunner 开始对测试用例的定义进行进一步的明确:
一条测试用例(testcase)应该是为了测试某个特定的功能逻辑而精心设计的,并且至少包含如下几点:
明确的输入(inputs)
明确的运行环境(execution conditions)
明确的测试步骤描述(testing procedure)
明确的预期结果(expected results)
对应地,HttpRunner 的测试用例描述方式进行如下设计:
测试用例应该是完整且独立的,每条测试用例应该是都可以独立运行的
在 HttpRunner 中,每个 YAML/JSON/pytest/go test 文件对应一条测试用例
HttpRunner 以 TestCase 为核心,将任意测试场景抽象为有序步骤的集合
包含且仅有两部分:
Config:测试用例的公共配置部分,包括用例名称、base_url、参数化数据源、是否开启 SSL 校验等
TestSteps:有序步骤的集合;采用了 go interface 的设计理念,支持进行任意协议和测试类型的拓展;步骤内容统一在 Run 方法中进行实现。
测试用例集(testsuite)
测试用例集 是 测试用例 的 无序 集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的。
测试场景
测试场景 和 测试用例集 是同一概念,都是 测试用例 的 无序 集合。
接口
测试用例集
参数
变量
测试脚本(YAML/JSON)
debugtalk.py
环境变量
项目根目录
项目根目录以debugtalk.py为锚, 测试用例中的相对路径(例如引用测试用例或CSV文件)都基于此根路径。
变量优先级
理解起来就是:局部优先于全局,离得越近,越优先。
测试用例变量(testcase variables) > export variables > testsuite config variables > 被引用用例配置变量(referenced testcase config variables)
优先级可能会令人困惑。 避免混淆的最好方法是使用不同的变量名。 但是,如果必须使用相同的变量名,则应该了解优先级策略。
版本对比
注意点:
Httprunner2 的用例目录是三级:api/case/suit,Httprunner3 的用例目录是:case/suit,但是生成的目录没有suit,需要的可以自己加。你可以将API定义为只有一个请求步骤的测试用例。
环境准备
测试环境python3.9,httprunner版本为3.1.11
安装指定版本
如果不指定版本号,默认会安装最新的4+版本。
安装
pip install httprunner==3.1.11
验证
httprunner -Vhrun -V
查看帮助信息
httprunner -h
usage: httprunner [-h] [-V] {run,startproject,har2case,make} ...One-stop solution for HTTP(S) testing.positional arguments: {run,startproject,har2case,make} sub-command help run Make HttpRunner testcases and run with pytest. startproject Create a new project with template structure. har2case Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner. make Convert YAML/JSON testcases to pytest cases.optional arguments: -h, --help show this help message and exit -V, --version show version
脚手架建项目
httprunner startproject HttpRunnerThreeDemo
目录说明
debugtalk.py(可选):存储项目中逻辑运算辅助函数
该文件存在时,将作为项目根目录定位标记,其所在目录即被视为项目工程根目录
该文件不存在时,运行测试的所在路径(CWD)将被视为项目工程根目录
测试用例文件中的相对路径(例如.csv)均需基于项目工程根目录
运行测试后,测试报告文件夹(reports)会生成在项目工程根目录
YAML/JSON/Python(必须):测试用例文件,存储接口测试相关信息
.env(可选):存储项目环境变量,通常用于存储项目敏感信息
reports:默认生成测试报告的存储文件夹
testcases: 测试用例存放目录
har 可以存放录制导出的文件
演练网站
本次依旧使用pity作为我们的测试网站,建议优先本地部署,方便调试以及后续的各种练习。用例中的接口我们采用F12的方式获得。
pity是一款专注于api自动化的工具,采用Python+FastApi+React开发。
项目实战
HttpRunner-小试牛刀
编写用例
# demo_testcase_login_test.py
config:
name: "request methods testcase: reference testcase"
teststeps:
- name: pity登录
request:
url: http://127.0.0.1:7777/auth/login
method: POST
headers: Content-Type: "application/json"
json:
"username": "tester"
"password": "tester"
extract:
- code: "json.code"
validate:
- eq: ["$code", 0]
运行用例
hrun demo_testcase_login.yml
通过上面,我们看到httprunner执行用例的时候会先生成py文件的测试用例,再以 pytest的方式运行测试用例。
生成用例
执行完yml文件会自动生成py文件的用例。
# NOTE: Generated By HttpRunner v3.1.11# FROM: testcases/demo_testcase_login.ymlfrom httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseDemoTestcaseLogin(HttpRunner):
config = Config("request methods testcase: reference testcase")
teststeps = [ Step( RunRequest("pity登录") .post("http://127.0.0.1:7777/auth/login") .with_headers(**{"Content-Type": "application/json"}) .with_json({"username": "tester", "password": "tester"}) .extract() .with_jmespath("body.code", "code") .validate() .assert_equal("$code", 0) ), ]
if __name__ == "__main__": TestCaseDemoTestcaseLogin().test_start()
同样的,我们可以直接执行刚刚生成的.py用例:
python demo_testcase_login_test.py
HttpRunner-用例转换
以后会搞个小小的专题,专门分析一下市面上的用例录制,转换,回放等工具的用法及对比,敬请期待。
HttpRunner-数据驱动
用例优先级遵循如下规则:
step variables > extracted variables, e.g. step 2, varA="step2A"
parameter variables > config variables, e.g. step 1, varB="paramB1"
extracted variables > parameter variables > config variables, e.g. step 2, varB="extractVarB"
config variables are in the lowest priority, e.g. step 1/2, varC="configC"
【注】step-步骤,parameter-参数,valuables-全局定义的。
config:
name: xxx
variables: # config variables
varA: "configA"
varB: "configB"
varC: "configC"
parameters: # parameter variables
varA: ["paramA1"]
varB: ["paramB1"]
teststeps:
- name: step 1
variables: # step variables
varA: "step1A"
request:
url: /$varA/$varB/$varC # varA="step1A", varB="paramB1", varC="configC"
method: GET
extract: # extracted variables
varA: body.data.A # suppose varA="extractVarA"
varB: body.data.B # suppose varB="extractVarB"
- name: step 2
varialbes:
varA: "step2A"
request:
url: /$varA/$varB/$varC # varA="step2A", varB="extractVarB", varC="configC"
method: GET
下面对上面的参数化方式进行举例,验证(为了讲解方便,未使用环境变量的参数化方式)。
HttpRunner-参数化1
在config:variables中编写测试数据,测试步骤中引用。
编写用例
config: name: "request methods testcase: params testcase" base_url: http://127.0.0.1:7777 variables: username: "tester" password: "tester"teststeps: - name: pity登录 request: url: /auth/login method: POST headers: Content-Type: "application/json" json: "username": $username "password": $password extract: - code: "json.code" validate: - eq: ["$code", 0]
运行用例
hrun hr_params/demo_testcase_login_params.yml
生成用例
执行完yml文件会自动生成py文件的用例。
# NOTE: Generated By HttpRunner v3.1.11# FROM: testcases/hr_params/demo_testcase_login_params.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseDemoTestcaseLoginParams(HttpRunner):
config = ( Config("request methods testcase: params testcase") .variables(**{"username": "tester", "password": "tester"}) .base_url("http://127.0.0.1:7777") ) teststeps = [ Step( RunRequest("pity登录") .post("/auth/login") .with_headers(**{"Content-Type": "application/json"}) .with_json({"username": "$username", "password": "$password"}) .extract() .with_jmespath("body.code", "code") .validate() .assert_equal("$code", 0) ), ]if __name__ == "__main__": TestCaseDemoTestcaseLoginParams().test_start()
HttpRunner-参数化2
conifg: parameters,teststeps:variables 中使用变量的情况。
编写用例
config: name: "request methods testcase: params testcase" base_url: ${ENV(BASE_URL)}# variables:# username: "tester"# password: "tester" parameters: username-password: - [ "tester","tester" ]teststeps: - name: pity登录 request: url: /auth/login method: POST headers: Content-Type: "application/json" json: "username": $username "password": $password extract: - code: "json.code" validate: - eq: ["$code", 0]
#.envUSERNAME=leoleePASSWORD=123456BASE_URL=http://127.0.0.1:7777
运行用例
加上-s打印的信息更全面。
hrun demo_testcase_login_params_parameters.yml -s
生成用例
执行完yml文件会自动生成py文件的用例。
# NOTE: Generated By HttpRunner v3.1.11# FROM: testcases/hr_params/demo_testcase_login_params_parameters.ymlimport pytestfrom httprunner import Parametersfrom httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseDemoTestcaseLoginParamsParameters(HttpRunner): @pytest.mark.parametrize( "param", Parameters({"username-password": [["tester", "tester"]]}) ) def test_start(self, param): super().test_start(param) config = Config("request methods testcase: params testcase").base_url( "${ENV(BASE_URL)}" ) teststeps = [ Step( RunRequest("pity登录") .post("/auth/login") .with_headers(**{"Content-Type": "application/json"}) .with_json({"username": "$username", "password": "$password"}) .extract() .with_jmespath("body.code", "code") .validate() .assert_equal("$code", 0) ), ]if __name__ == "__main__": TestCaseDemoTestcaseLoginParamsParameters().test_start()
HttpRunner-参数化3
登录---增加项目---查询项目,一个复杂点的例子。
step variables ,extract variables,parameter variables ,config variables变量优先级使用情况。大家可以将本例中的参数随意更改,亲手验证。
经过思考,我认为我们可以选择一种适合自己的方式进行参数化。实际过程中也就调试的时候会将值写死在步骤中。为了方便,建议是最好写在config中,其中写到parameters最合适不过了,这样省去了后续数据驱动的麻烦。
编写用例
config: name: "request methods testcase: params testcase" base_url: http://192.168.0.123:7777 # 本机的pity不工作了,赶紧换了个电脑,大佬们看到了可以改成自己的IP哦--彩蛋。# variables:# username: "tester_config_var1"# password: "tester_config_var1" parameters: username-password-name-app: - ["tester","tester","aa","aa"]teststeps: - name: pity登录 request: url: /auth/login method: POST headers: Content-Type: "application/json" json: "username": $username "password": $password extract: - code: "json.code" - token: body.data.token validate: - eq: ["$code", 0] - name: 创建项目 request: url: /project/insert method: POST headers: Content-Type: "application/json" token: "$token" json: {"name": "${name}","app":"${app}","owner":2,"private":false} extract: - code: "json.code" ## 提取到的变量,我们后面涉及到关联的再介绍它 validate: - eq: [ "$code", 0 ] - name: 查询项目信息 request: url: /project/list method: GET headers: Content-Type: "application/json" token: "$token" params: page: 1 size: 8 extract: - code: "json.code" - name: "json.data[0].name" validate: - eq: ["$code", 0] - eq: ["$name", "${name}"]
运行用例
hrun create_project_params_parameters.yml
生成用例
执行完yml文件会自动生成py文件的用例。
# NOTE: Generated By HttpRunner v3.1.11# FROM: testcases/hr_params2/create_project_params_parameters.ymlimport pytestfrom httprunner import Parametersfrom httprunner import HttpRunner, Config, Step, RunRequest, RunTestCaseclass TestCaseCreateProjectParamsParameters(HttpRunner): @pytest.mark.parametrize( "param", Parameters({"username-password-name-app": [["tester", "tester", "aa", "aa"]]}), ) def test_start(self, param): super().test_start(param) config = Config("request methods testcase: params testcase").base_url( "http://192.168.0.123:7777" ) teststeps = [ Step( RunRequest("pity登录") .post("/auth/login") .with_headers(**{"Content-Type": "application/json"}) .with_json({"username": "$username", "password": "$password"}) .extract() .with_jmespath("body.code", "code") .with_jmespath("body.data.token", "token") .validate() .assert_equal("$code", 0) ), Step( RunRequest("创建项目") .post("/project/insert") .with_headers(**{"Content-Type": "application/json", "token": "$token"}) .with_json( {"name": "${name}", "app": "${app}", "owner": 2, "private": False} ) .extract() .with_jmespath("body.code", "code") .validate() .assert_equal("$code", 0) ), Step( RunRequest("查询项目信息") .get("/project/list") .with_params(**{"page": 1, "size": 8}) .with_headers(**{"Content-Type": "application/json", "token": "$token"}) .extract() .with_jmespath("body.code", "code") .with_jmespath("body.data[0].name", "name") .validate() .assert_equal("$code", 0) .assert_equal("$name", "${name}") ), ]if __name__ == "__main__": TestCaseCreateProjectParamsParameters().test_start()
创建成功
HttpRunner-参数化4
hrun create_project_params_debugtalk.yml
在debugtalk.py中参数化,详见后面的代码地址,这里不再展示。
注意:
step中不支持从debugtalk中读取函数返回值。
小小的总结一下下
配置变量(config variables):用于数据解藕:字典类型,定义在config或者teststep中。
参数变量(parameter variables):用户参数化,列表类型,定义在config。
HttpRunner-testsuit使用
编写用例
config: name: "demo testsuite"testcases: - name: "suitdemo" testcase: hr_params2/create_project_params_debugtalk.yml #以源码为准,路径太长,我这里写不下了。
运行用例
hrun create_project_testsuit.yml
生成报告
hrun create_project_testsuit.yml --html=report/report.html
关于用例套件也涉及到变量的问题,此处只给出结论,以后再讲吧,写不动了。
测试用例变量(testcase variables) > export variables > testsuite config variables > 被引用用例配置变量(referenced testcase config variables)
config: name: xxx variables: # testsuite config variables varA: "configA" varB: "configB" varC: "configC"testcases:- name: case 1 variables: # testcase variables varA: "case1A" testcase: /path/to/testcase1 export: ["varA", "varB"] # export variables- name: case 2 varialbes: # testcase variables varA: "case2A" testcase: /path/to/testcase2
HttpRunner-hooks机制
牛刀小试
编写用例
config: name: "request methods testcase: params testcase" base_url: http://192.168.0.123:7777 # 本机的pity不工作了,赶紧换了个电脑,大佬们看到了可以改成自己的IP哦--彩蛋 variables: username: "tester" password: "tester"teststeps: - name: pity登录 request: url: /auth/login method: POST headers: Content-Type: "application/json" json: "username": $username "password": $password extract: - code: "json.code" - token: body.data.token validate: - eq: ["$code", 0] - name: 创建项目 setup_hooks: - ${hook_before_add($request)} teardown_hooks: - ${hook_after_teardown($response)} request: url: /project/insert method: POST headers: Content-Type: "application/json" token: "$token" data: {"name": "aaa","app":"aaa","owner":2,"private":false} extract: - code: "json.code" ## 提取到的变量,我们后面涉及到关联的再介绍它 validate: - eq: [ "$code", 0 ]
# debugtalk.pydef hook_before_add(request): print("====") print(request) data={} data['name']="bbb" data['app']="bbb" data['owner']=2 data['private']=False request['data']=json.dumps(data)def hook_after_teardown(response): print("==========") print(response.json)
运行用例
hrun create_project_hooks.yml -s
创建成功
成功的将用例中的项目由aaa修改为bbb,说明我们的hook调用成功了。
文章源码
https://gitee.com/rdtest/code2022/tree/master/HttpRunnerThreeDemo