使用Gatling进行软件性能测试,高级关联技术是处理现代RESTful API和复杂应用场景的重要技能。当响应中包含嵌套JSON、动态数组或参数依赖前序上下文时,能否精准地提取并传递这些动态值,决定了测试脚本的可靠性和真实性。
为何需要高级关联?
简单关联(如提取固定字段)在以下复杂响应前会失效:
数据结构嵌套:所需参数深藏在多级JSON节点下。
数组长度和内容动态:数组大小、元素顺序每次请求都可能变化,需动态定位。
参数上下文依赖:后续请求的路径、查询参数或请求体,依赖于前面多个请求提取值的组合计算。
技术解析
1. 处理嵌套JSON:使用精确的JSONPath
当目标值嵌套在多层JSON结构中时,必须使用准确的JSONPath表达式定位。
示例场景:登录后返回的用户信息包含嵌套的联系方式。
json
{
"user": {
"id": 12345,
"profile": {
"name": "张三",
"contact": {
"email": "zhangsan@example.com",
"phone": "13800138000"
}
}
}
}
Gatling关联实现:
scala
.exec(
http("获取用户信息")
.get("/api/user")
.check(
// 使用JSONPath提取嵌套字段
jsonPath("$.user.id").saveAs("userId"),
jsonPath("$.user.profile.contact.email").saveAs("userEmail"),
jsonPath("$.user.profile.contact.phone").saveAs("userPhone")
)
)
// 在后续请求中使用提取的值
.exec(
http("更新联系方式")
.put("/api/user/${userId}/contact")
.body(StringBody("""{"email": "${userEmail}", "phone": "${userPhone}"}"""))
)
重点:使用$.表示根节点,然后精确描述路径。Gatling的jsonPath检查器支持标准的JSONPath语法。
2. 处理动态数组:随机选择、条件匹配和遍历
从JSON数组或HTML列表中提取一个动态出现的元素是常见需求。
示例场景:查询商品列表,返回一个动态的商品ID数组,需要随机选择一个加入购物车。
json
{
"products": [
{"id": 1001, "name": "商品A", "stock": 5},
{"id": 1002, "name": "商品B", "stock": 0},
{"id": 1003, "name": "商品C", "stock": 12}
]
}
Gatling关联实现:
scala
.exec(
http("查询商品列表")
.get("/api/products")
.check(
// 方法1:提取整个ID数组到会话列表中
jsonPath("$.products[*].id").findAll.saveAs("productIdList"),
// 方法2:直接随机提取一个有库存的商品ID(使用条件JSONPath)
jsonPath("$.products[?(@.stock > 0)].id").findRandom.saveAs("randomProductId")
)
)
.exec(session => {
// 演示如何操作提取的数组
val ids = session("productIdList").as[Seq[String]]
println(s"获取到的商品ID列表: $ids")
session
})
.exec(
http("添加随机商品到购物车")
.post("/api/cart/add")
// 使用之前随机提取的单个ID
.body(StringBody("""{"productId": "${randomProductId}"}"""))
)
主要方式:
findAll:提取所有匹配项为Seq[String],可用于后续逻辑。
findRandom:随机抽取一个,非常适合模拟用户随机选择。
条件JSONPath:[?(@.stock > 0)] 是强大过滤器,可用于提取符合业务状态的元素。
3. 处理上下文相关参数:组合、转换和链式传递
最复杂的情况是参数需要经过计算,或依赖多个前序步骤的上下文。
示例场景:创建订单后支付,支付接口需要“订单ID”和“订单金额”,而金额需根据订单详情二次查询。
scala
// 第一步:创建订单,提取订单ID
.exec(
http("创建订单")
.post("/api/order")
.body(ElFileBody("templates/create_order.json"))
.check(jsonPath("$.orderId").saveAs("orderId"))
)
// 第二步:使用上一步的orderId查询订单详情,提取金额
.exec(
http("查询订单详情")
.get("/api/order/${orderId}") // 使用第一个参数
.check(jsonPath("$.totalAmount").saveAs("orderAmount"))
)
// 第三步:组合前两步的参数,发起支付
.exec(
http("订单支付")
.post("/api/payment")
.body(StringBody("""{"orderId": "${orderId}", "amount": "${orderAmount}", "currency": "CNY"}"""))
.check(jsonPath("$.paymentId").saveAs("paymentId"))
)
// 第四步:可能继续使用支付ID查询状态...
在Session中执行计算
如果参数需要简单计算(如添加时间戳、拼接字符串),可在 exec 代码块中操作Session。
scala
.exec(session => {
val orderId = session("orderId").as[String]
val timestamp = System.currentTimeMillis()
// 生成一个唯一的支付流水号
val transactionId = s"PAY-${orderId}-${timestamp}"
session.set("transactionId", transactionId)
})
调试技巧
使用检查点:在关联后添加检查点,保证参数被正确提取。
scala
.check(jsonPath("$.orderId").exists)
优先使用CSS(对于HTML)和JSONPath(对于JSON):比正则表达式更易读、更稳定。
防御性编程:考虑提取可能失败的情况,使用 findAll.optional 或通过 session 判断,避免脚本因单次失败而中止。
记录和调试:在开发脚本阶段,使用 exec(session => { println(session); session }) 打印完整会话,确认提取的值。
模块化设计:将复杂的关联逻辑封装到自定义方法或对象中,保持场景代码的清晰。
Gatling的高级关联技术在于:精准定位、灵活提取和上下文管理。通过组合运用这些技术,可以构建出能够处理任何动态、嵌套API响应的强大性能测试脚本。