接口测试平台(一)httprunner
我准备写一个基于httprunner的接口自动化测试平台了,所以先介绍一下httprunner
httprunner
httpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型。简单易用,功能强大,具有丰富的插件化机制和高度的可扩展能力。
设计理念
- 约定大于配置:测试用例是标准结构化的,格式统一,方便协作和维护
- 标准开放:基于开放的标准,支持与 HAR/Postman/Swagger/Curl/JMeter 等工具对接,轻松实现用例生成和转换
- 一次投入多维复用:一套脚本可同时支持接口自动化测试、性能测试、数字体验监测等多种 API 测试需求
- 融入最佳工程实践:不仅仅是一款测试工具,在功能中融入最佳工程实践,实现面向网络协议的一站式测试解决方案
核心特性
- 网络协议:完整支持 HTTP(S)/HTTP2/WebSocket,可扩展支持 TCP/UDP/RPC 等更多协议
- 多格式可选:测试用例支持 YAML/JSON/go test/pytest 格式,并且支持格式互相转换
- 双执行引擎:同时支持 golang/python 两个执行引擎,兼具 go 的高性能和 pytest 的丰富生态
- 录制 & 生成:可使用 HAR/Postman/Swagger/curl 等生成测试用例;基于链式调用的方法提示也可快速编写测试用例
- 复杂场景:基于 variables/extract/validate/hooks 机制可以方便地创建任意复杂的测试场景
- 插件化机制:内置丰富的函数库,同时可以基于主流编程语言(go/python/java)编写自定义函数轻松实现更多能力
- 性能测试:无需额外工作即可实现压力测试;单机可轻松支撑 1w+ VUM,结合分布式负载能力可实现海量发压
- 网络性能采集:在场景化接口测试的基础上,可额外采集网络链路性能指标(DNS 解析、TCP 连接、SSL 握手、网络传输等)
- 一键部署:采用二进制命令行工具分发,无需环境依赖,一条命令即可在 macOS/Linux/Windows 快速完成安装部署
安装说明
参考官网:https://httprunner.com/docs/user-guide/installation/
windows安装
pip命令行安装:pip install httprunner
查看安装是否成功:httprunner -V
安装har2case:pip install har2case
安装httprunner:将httprunner下载解压到本地路径,在系统环境变量中维护该路径
增强测试用例
变量
变量声明
在 HttpRunner 测试用例中,有 4 个地方可以对变量进行声明。
声明全局变量(config variables)
在 config 下声明的 variables 为测试用例全局变量,作用域为整个测试用例,在测试用例的所有地方都可以引用。
声明数据驱动(parameters)
在 config 下声明的 parameters 为测试用例的驱动参数;它的作用域也是覆盖整个测试用例,在测试用例的所有地方都可以引用。
声明局部变量(teststeps variables)
在单个测试步骤(teststep)下声明的 variables 是测试步骤局部变量,作用域仅限当前步骤。
各个测试步骤的变量相互独立,互不影响。
提取参数变量(session variables)
还有一种变量声明的方式,可以在某个测试步骤(teststep)中提取(extract)特定的响应参数,并赋值给指定的变量名。该操作也常被成为 参数关联。
提取的参数变量类似于 session 参数,作用域为当前步骤及之后的步骤。
变量引用
在 HttpRunner 的测试用例中,约定通过 ${} 或 $ 的形式来引用变量。
例如:$var 或 ${var}
大多数情况下,采用 $var 或 ${var} 这两种形式都是可以的。但如果在某些字段中存在部分引用变量的情况,例如 abc123def 中 123 需要引用变量,那么就只能使用 ${var} 的形式,即 abc${num}def;如果使用 abc$numdef 的话,变量名称会被识别为 numdef。
另一种需要说明的情况,如果在测试用例中本身就存在 $ 符号,那么可以通过 $$ 进行转义。
例如,测试用例中某个字段的原始内容为 $m,那么为了避免将其解析为变量,则需要将其写为 $$m。
变量优先级
针对上述的 4 类变量类型,如果声明的变量名称出现重复,则会按照一定的优先级策略进行处理。
优先级从高到低依次为:step variables > session variables > parameter variables > config variables
示例
config:{
"name": xxx
"variables": { # config variables
"varA": "configA"
"varB": "configB"
"varC": "configC"
}
"parameters" : { # parameters variables
"varA-varB": [
["paramA1", "paramB1"],
["paramA2", "paramB2"],
]
}
}
teststeps:[
{
"name": "step 1"
"variables": { # step variables
"varA": "step1A"
},
"request": {
"method" : "GET"
"url" : /$varA/$varB/$varC # varA="step1A", varB="paramB1", varC="configC"
}
"extract": {
"varA": "body.data.A", # suppose varA="extractVarA"
"varB": "body.data.B" # suppose varB="extractVarB"
}
},
{
"name": "step 2"
"varialbes": { # step variables
"varA" : "step2A"
}
"request": {
"url" : /$varA/$varB/$varC # varA="step2A", varB="extractVarB", varC="configC"
"method" : "GET"
}
}
]
在步骤 1 中:
varA 定义了局部变量,优先级最高,因此实际值为 step1A
varB 未定义局部变量,将继承全局变量;而在全局变量中,parameter 的优先级高于 variables,因此 varB 实际值为 paramB1
varC 未定义局部变量,将继承全局变量;在全局变量中,varC 仅在 variables 中进行了声明,因此 varC 的实际值为 configC
在步骤 2 中:
varA 定义了局部变量,优先级最高,因此实际值为 step2A
varB 未定义局部变量;而步骤 1 中有提取该变量,优先级高于全局变量,因此 varB 实际值为 extractVarB
varC 未定义局部变量,也不存在同名 session 变量,将继承全局变量,因此 varC 实际值为 configC
参数提取
目前,HttpRunner 支持 2 种响应结果字段提取方式。
提取的参数变量类似于 session 参数,作用域为当前步骤及之后的步骤,引用方式与普通的变量一致。
jmespath 表达式
若响应结果为 JSON 结构,支持采用 jmespath 表达式进行参数提取。
jmespath 是一种 JSON 查询语言,可以使用非常灵活且强大的表达式查询 JSON 数据结构中的字段,并返回符合条件的数据。
示例:
{
"locations": [
{"name": "Seattle", "state": "WA"},
{"name": "New York", "state": "NY"},
{"name": "Bellevue", "state": "WA"},
{"name": "Olympia", "state": "WA"}
]
}
- 查询所有城市名称:locations[*].name
- 查询第二个城市名称:locations[1].name => “New York”
- 查询最后一个州名:locations[-1].state => “WA”
1、extract 的对象仅有 5 种类型:
status_code:提取响应状态码,例如 200、404
proto:提取协议类型,例如 “HTTP/2.0”、“HTTP/1.1”
headers:从响应 headers 中提取字段,例如 headers.name
cookies:从响应 cookies 中提取字段,例如 cookies.Token
body:从响应 body 中提取字段,例如 body.args.foo1
2、如果表达式中存在 - 的情况,那么需要加引号处理。
例如 headers."Content-Type"
"teststeps": [
{
"name": "",
"variables": {
"name": "demo"
},
"request": {
"method": "POST",
"url": "https://www.httpbin.org",
"params": {},
"headers": {
"name": "$name",
"Content-Type": "text/plain"
},
"body": ""
},
"validate": [],
"extract": {
"name": "body.headers.Name"
}
}
]
正则表达式
若响应结果为 text/html 格式,支持采用正则表达式的方式提取目标参数。
例如响应的 body 为:
<html>
<title>参数提取(extract) | HttpRunner</title>
</html>
如果我们想提取页面的 title 字段,就可以这样做:
即在提取表达式中指定目标参数的左右边界,然后将目标参数替换为 (.*);这样我们就能将正则匹配到的值赋值给参数变量了。
"teststeps": [
{
"name": "",
"variables": {
"name": "demo"
},
"request": {
"method": "GET",
"url": "https://www.httpbin.org",
"params": {},
"headers": {
"name": "$name",
"Content-Type": "text/plain"
}
},
"validate": [],
"extract": {
"title": "<title>(.*)</title>"
}
}
]
自定义函数
使用 python 语言编写方式,仅需在项目根目录 debugtalk.py 中编写自定义函数即可,无需进行额外编译。同时,使用 hrp run/boom 也兼容 v4.0 版本之前的写法。
import logging
import time
from typing import List
def get_user_agent():
return "hrp/funppy"
def sleep(n_secs):
time.sleep(n_secs)
def sum(*args):
result = 0
for arg in args:
result += arg
return result
def sum_ints(*args: List[int]) -> int:
result = 0
for arg in args:
result += arg
return result
def sum_two_int(a: int, b: int) -> int:
return a + b
def sum_two_string(a: str, b: str) -> str:
return a + b
def sum_strings(*args: List[str]) -> str:
result = ""
for arg in args:
result += arg
return result
def concatenate(*args: List[str]) -> str:
result = ""
for arg in args:
result += str(arg)
return result
def setup_hook_example(name):
logging.warning("setup_hook_example")
return f"setup_hook_example: {name}"
def teardown_hook_example(name):
logging.warning("teardown_hook_example")
return f"teardown_hook_example: {name}"
注意:使用 hrp 进行测试时,hrp 会根据 debugtalk.py 文件生成程序可直接调用的 .debugtalk_gen.py 文件,请勿编辑。
运行接口测试
测试报告
用 hrp run 执行接口测试时,我们可以通过添加启动参数 --gen-html-report 在当前项目根目录的 reports 中生成一份 HTML 格式的测试报告。测试报告的输出名称为:report-unix时间戳。
报告样式:
报告样式如下所示,与 HttpRunner v2.x 版本保持一致。
报告说明:
在 Summary 中,会罗列本次测试的整体信息,包括测试开始时间、总运行时长、运行的 Go 版本和系统环境、运行结果统计数据。
在 Details 中,会详细展示每一条测试用例的运行结果。
其中,每一条测试用例对应一个 log 按钮,点击 log 按钮,会在弹出框中展示该用例执行的详细数据,包括请求的 headers 和 body、响应的 headers 和 body、校验结果、响应、响应耗时(elapsed)等信息。具体细节如下图所示:
此外,若测试用例运行不成功(error),则在该测试用例的 Detail 中会出现 traceback 按钮,点击该按钮后,会在弹出框中展示失败原因。
综上所述,我之所以选择基于httprunner来开发接口测试平台,是因为他可以通过录制自动生成用例,并且支持不同格式用例相互转换,json格式用例在前后端交互过程中也方便处理数据。