JMeter 断言与事务控制:别让压测结果“假绿”¶
压测最常见的翻车方式:报告错误率 0.00%,业务同事说“下单成功率只有 60%”。原因是只校验了 HTTP 200。
1. 一个真实的假绿案例¶
接口返回:
HTTP 状态码 200。没有断言时 JMeter 记为 success,900 并发里 400 个库存不足全被算成成功。
修法不是加一句“包含 200”,而是断言业务码:
再补一条“关键字段非空”:
JSON Assertion
Assert JSON Path exists: $.data.orderId
Additionally assert value: false
Expect null: false
2. 断言放哪:作用域陷阱¶
| 位置 | 实际作用 |
|---|---|
| Sampler 子节点 | 只对该请求生效(推荐) |
| Transaction Controller 子节点 | 对事务内所有请求生效 |
| Thread Group 子节点 | 对所有请求生效,容易误伤 |
| 与 Sampler 同级 | 作用于同级的后续 Sampler,顺序错了就失效 |
断言和 Sampler 同级是最常见的错误写法,表现是“有时生效有时不生效”。统一放成 Sampler 的子节点。
3. JSR223 断言:处理复杂业务校验¶
JSON Assertion 只能做等值/存在判断。金额计算、状态机校验用 Groovy:
import groovy.json.JsonSlurper
def body = prev.getResponseDataAsString()
def json = new JsonSlurper().parseText(body)
if (json.code != 0) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("业务失败 code=" + json.code + " msg=" + json.msg)
return
}
// 金额必须等于单价 × 数量
def expected = new BigDecimal(json.data.unitPrice.toString())
.multiply(new BigDecimal(json.data.count.toString()))
.setScale(2, BigDecimal.ROUND_HALF_UP)
def actual = new BigDecimal(json.data.amount.toString()).setScale(2)
if (expected.compareTo(actual) != 0) {
AssertionResult.setFailure(true)
AssertionResult.setFailureMessage("金额不符 expected=${expected} actual=${actual}")
}
要点:
prev是当前 Sampler 的结果对象,prev.getResponseDataAsString()拿响应体- 不要用
vars.get("...")拿响应,那是变量不是响应 - Groovy 里金额一律
BigDecimal,用double会出现0.1+0.2=0.30000000000000004 - 断言脚本要勾选
Cache compiled script if available,否则每个请求都编译一次,压测机 CPU 会被吃掉
4. 其他常用断言¶
| 断言 | 场景 | 关键配置 |
|---|---|---|
| Response Assertion | 校验状态码/文本 | Response Code 等于 200;文本用 Contains |
| Duration Assertion | 单请求 RT 上限 | 500ms,超过即失败 |
| Size Assertion | 防止空响应 | bytes > 100 |
| XPath Assertion | 老系统 XML | 勾选 Validate XML |
| JSR223 Assertion | 复杂逻辑 | 见上 |
Duration Assertion 只在需要严格 SLA 的接口上加。全量加会让长尾请求全变红,报告失去区分度。
5. 事务控制器:统计口径¶
勾选:
Generate parent sample:只显示事务整体,子请求不单独计入统计Include duration of timer and pre-post processors:是否把思考时间算进事务
压测要的是端到端耗时,通常勾选第一项、不勾第二项(思考时间是用户行为,不是系统耗时)。
6. 事务耗时的三个坑¶
坑 1:事务 RT 远大于子请求之和
原因通常是勾了 Include duration of timer,把 1-3 秒思考时间算进去了。表现为事务 P95 = 3500ms,而三个子请求 P95 都在 300ms。
坑 2:事务里的断言失败,子请求仍是绿色
断言放在事务控制器下,失败只体现在事务上。排查时要展开事务看子请求状态。
坑 3:事务名带参数,统计被打散
事务名写成 下单_${orderId},聚合报告里会出现几千个 label。事务名必须是固定字符串。
7. 断言开销有多大¶
同一脚本、同一压力,开/关断言的对比(8C16G 压测机,单请求 2KB JSON):
| 配置 | TPS | 压测机 CPU |
|---|---|---|
| 无断言 | 3100 | 42% |
| JSON Assertion ×2 | 2870 | 55% |
| JSR223 Groovy(未缓存编译) | 1500 | 92% |
| JSR223 Groovy(缓存编译) | 2760 | 58% |
结论:JSON Assertion 优先,JSR223 必须开脚本缓存,复杂度控制在一两个判断内。
8. 验证断言真的生效¶
# 1. 故意把期望值改错,跑 1 线程 1 循环
jmeter -n -t order.jmx -l check.jtl
# 2. 确认 jtl 中该请求 success=false
awk -F, 'NR>1 {print $3, $8, $9}' check.jtl | head
# 3. 打开日志看断言消息
# log4j2.xml 中把 jmeter.assertions 设为 debug
9. 断言设计原则¶
- 每条断言只回答一个问题:业务成功了吗
- 断言业务码与关键字段,不断言整个响应体
- 不断言时间戳、自增 ID、跟踪号等每次都变的值
- 错误消息里带上响应片段,便于定位
- 压测前先跑通功能(1 线程),再逐步加压