第五章. 断言与逻辑控制:让脚本有脑子

第五章. 断言与逻辑控制:让脚本有 “脑子”

摘要:本章将解决 JMeter “盲目乐观” 的问题。默认情况下,只要服务器返回 HTTP 200,JMeter 就会判定测试通过,忽略了业务逻辑报错(如 “库存不足”)。我们将学习如何使用 断言 识别真正的业务失败,并利用 逻辑控制器 让脚本根据上一步的结果智能决定下一步的走向。

本章学习路径

我们将按照以下步骤打造智能脚本:

  • 5.1 揭穿 “假成功” 现象
    • 5.1.1 构造一个 “HTTP 200 但业务失败” 的接口
    • 5.1.2 观察 JMeter 的误判
  • 5.2 响应断言 (Response Assertion)
    • 5.2.1 校验核心业务字段
    • 5.2.2 设定自定义的失败消息
  • 5.3 JSON 断言 (JSON Assertion)
    • 5.3.1 精准校验结构化数据
    • 5.3.2 验证数组长度与特定值
  • 5.4 逻辑控制器 (If Controller)
    • 5.4.1 场景:登录失败就不下单
    • 5.4.2 JEXL3 表达式语法实战

5.1. 揭穿 “假成功” 现象

在上一章,我们完成了关联。但在实际开发中,接口返回 HTTP 200 并不代表业务成功。例如支付接口返回 {"code": 5001, "msg": "余额不足"},HTTP 状态码依然是 200。如果不加判断,JMeter 会认为这次请求是成功的,导致最终的压测报告显示 “100% 成功率”,这不仅误导,甚至可能掩盖严重的线上 Bug。

5.1.1. 改造靶场:模拟业务异常

我们需要修改 Spring Boot 代码,增加一个模拟库存扣减的接口,它会随机返回成功或失败。

文件路径src/main/java/com/demo/jmeterdemo/controller/OrderController.java

请新建 OrderController 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.demo.jmeterdemo.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

@RestController
@RequestMapping("/api/order")
public class OrderController {

/**
* 模拟下单接口
* 场景:30% 的概率因为 "库存不足" 而下单失败
* 注意:无论成功失败,HTTP 状态码都是 200
*/
@PostMapping("/create")
public Map<String, Object> create() {
Map<String, Object> result = new HashMap<>();
// 模拟 30% 的失败率
boolean isStockEnough = ThreadLocalRandom.current().nextInt(100) > 30;
if (isStockEnough) {
result.put("code", 200);
result.put("msg", "Success");
result.put("orderId", System.currentTimeMillis());
} else {
// 业务错误,但 HTTP 依然是 200
result.put("code", 5001);
result.put("msg", "Out of Stock");
}
return result;
}
}

重启项目,我们准备开始测试。

5.1.2. 观察 JMeter 的误判

  1. 在 JMeter 中新建线程组 " S02_断言测试 "。

  2. 添加 HTTP 请求,命名为 " API_下单 "。

配置如下参数:

  • Path: /api/order/create
  • Method: POST
  1. 设置线程组 循环次数10

  2. 运行并观察 察看结果树

结果分析:你会发现 10 次请求全部都是 绿色盾牌。点开响应数据,你会发现其中夹杂着 {"code": 5001, "msg": "Out of Stock"}

这就是 “假成功”。在真正的压测报告中,我们必须把这 30% 的库存不足标记为 “失败”,否则我们就不知道系统在什么并发量下会开始出现业务瓶颈。


5.2. 响应断言:最通用的门神

为了纠正 JMeter 的判断,我们需要添加 断言 (Assertion)。断言就是我们设定的 “通过标准”。

5.2.1. 添加响应断言

操作步骤

  1. 右键点击 " API_下单 " -> 添加 -> 断言 -> 响应断言 (Response Assertion)
  2. 配置以下参数:
参数项配置值解释
测试字段 (Apply to)Main sample only只检查主请求。
测试字段 (Field to Test)响应文本 (Text Response)检查 Response Body 的内容。
模式匹配规则包括 (Contains)只要包含指定字符串就算通过。
测试模式 (Patterns to Test)"code": 200点击【添加】按钮输入。这里我们强制要求返回结果必须包含这段 JSON 文本。
自定义失败消息业务代码非 200如果断言失败,结果树中会显示这句话,方便排查。

5.2.2. 验证效果

再次运行测试。

预期结果:在 察看结果树 中,你应该会看到大约 30% 的请求变成了 红色感叹号。点击红色的请求,展开 断言结果 (Assertion Failure Message),你会看到:
Assertion error: false that ... contains "code": 200

image-20251120103406076

这样,JMeter 的聚合报告中的 “Error %” 就能真实反映业务成功率了。


5.3. JSON 断言:结构化数据的专家

虽然 “响应断言” 简单好用,但通过字符串匹配 "code": 200 并不严谨。如果返回的 msg 里包含 code: 200 字符,可能会导致误判。

对于 JSON 接口,更推荐使用 JSON 断言

5.3.1. 配置 JSON 断言

我们先禁用掉刚才的 “响应断言”(右键 -> 禁用),添加一个新的断言。

操作步骤

  1. 右键点击 " API_下单 " -> 添加 -> 断言 -> JSON 断言 (JSON Assertion)
  2. 配置参数:
参数项配置值解释
Assert JSON Path exists$.orderId我们要求返回结果中必须包含 orderId 字段。只有下单成功才有这个字段。
Expected Value (可选)(空)如果勾选了 Additionally assert value,可以校验字段的值。这里我们只校验 “是否存在”。

原理:当返回 {"code": 5001, "msg": "Out of Stock"} 时,JSON 中没有 orderId 字段,断言通过 JSON Path 找不到路径,判定为失败。


5.4. 逻辑控制器:让脚本学会 “止损”

默认情况下,JMeter 像一个只会按顺序执行命令的机器人:无论上一步是成功还是炸了,它都会坚定地执行下一步。但在真实的业务场景中,如果用户连 “登录” 都失败了,后续的 “下单”、“支付” 操作根本就不应该发生。继续执行这些无效请求,不仅浪费压测机的 CPU,还会让服务端产生大量无意义的 401 错误日志,干扰问题排查。

我们需要利用 If 控制器 (If Controller) 来实现逻辑判断:只有当条件满足时,才执行内部的组件。

5.4.1. 场景重构:依赖链路

为了演示这个功能,我们需要构建一个典型的依赖场景:

  1. 前置动作:用户尝试登录。
  2. 判断依据:提取登录接口返回的状态码 code
  3. 分支逻辑
    • 如果 code == 200:执行 “API_下单”。
    • 如果 code != 200:直接跳过,不做任何操作。

准备工作:请确保你的 “API_登录” 请求下已经挂载了 JSON 提取器,并将状态码提取为变量 login_code

image-20251120105307483

5.4.2. 借助函数助手生成表达式

在配置 If 控制器之前,我们先解决最难的一步:如何写出正确的判断表达式?

手动编写 ${__jexl3(...)} 既容易错又难记。我们要利用 函数助手 自动生成代码。

操作步骤

  1. 点击 JMeter 顶部菜单栏的 工具 (Tools) -> 函数助手对话框 (Function Helper Dialog)

  2. 在左侧列表中找到并选中 __jexl3(这是 JMeter 高性能运算函数)。

  3. 在右侧的 参数值 栏位中,输入你的逻辑表达式:

    "${login_code}" == "200"

  4. 点击底部的 生成 (Generate) 按钮。

  5. 复制 生成的字符串:${__jexl3("${login_code}" == "200",)}

语法细节:为什么变量 ${login_code} 外面要加双引号?这是为了防止空指针。如果提取失败,变量为空,表达式会变成 "" == "200"(合法);如果不加引号,会变成 == "200"(语法错误)。

5.4.3. 配置 If 控制器

拿到生成的代码后,配置控制器就非常简单了。

操作步骤

  1. 右键点击 线程组 -> 添加 -> 逻辑控制器 -> 如果 (If) 控制器
  2. 将 “API_下单” 请求 拖拽 到 “If 控制器” 的内部(使其成为子节点)。
  3. 在 If 控制器的面板中进行粘贴:
    • Expression:粘贴刚才复制的代码 ${__jexl3("${login_code}" == "200",)}
    • Interpret Condition as Variable Expression?必须勾选

始终勾选 “Interpret Condition as Variable Expression?”。这告诉 JMeter 直接使用高性能的 JEXL3 引擎处理变量,而不是启动笨重的 JavaScript 引擎。在高并发压测下,这一项配置能提升 10 倍以上的性能。

5.4.4. 验证 “止损” 效果

配置完成后,我们进行正反两面的测试,验证逻辑是否生效。

测试 A:正向用例(登录成功)

  1. 修改登录接口参数为正确的账号密码(admin/123456)。
  2. 运行脚本。
  3. 观察login_code 变为 200,你会在结果树中同时看到 “API_登录” 和 “API_下单”。

测试 B:反向用例(登录失败)

  1. 修改登录接口参数为错误的密码(如 admin/error)。
  2. 运行脚本。
  3. 观察
    • “API_登录” 执行,但业务码不是 200。
    • 关键点:在结果树中,完全看不到 “API_下单” 的记录
    • 这说明 If 控制器成功拦截了请求,脚本实现了智能止损。

image-20251120110600928


5.5. 本章小结

本章我们给脚本赋予了 “判断能力” 和 “决策能力”。

核心要点

  1. HTTP 200 不等于成功:压测必须基于业务指标,使用 响应断言JSON 断言 来修正成功率统计。
  2. 断言选择:简单的文本包含用响应断言;复杂的 JSON 字段校验用 JSON 断言。
  3. If 控制器:用于构建依赖链路,避免上游失败后下游继续无效执行,节省压测机资源。

下一步计划:目前我们的脚本已经非常健壮了,不仅能传参,还能自动判断对错。但是,它们还只能运行在标准 Java 代码无法解决的逻辑上。比如:“我想对密码进行 RSA 加密后再发送”,或者 “我想直接连数据库清理垃圾数据”。这些需求靠标准组件很难实现。在下一章,我们将解锁 JMeter 的核武器——JSR223 + Groovy 脚本编程