JMeter 实战指南:从入门到复杂场景并发测试
作为后端开发者,确保我们构建的应用在高并发场景下依然稳定高效是至关重要的。Apache JMeter 作为一款开源的、基于 Java 的压力测试工具,因其功能全面、使用便捷且跨平台(Linux/Windows/macOS),成为了我们进行接口测试与性能(压力)测试的首选工具之一。本文将引导您从 JMeter 的基础配置入手,逐步构建一个涉及登录、数据提取、多步骤依赖的复杂场景并发测试。
前提条件: 由于 JMeter 是 Java 开发的,请确保您的测试环境中已正确安装 JDK。
JMeter 下载: JMeter 官网
一、JMeter 核心组件概览
在深入实践之前,我们先简单了解一下 JMeter 的几个核心组件,这有助于我们理解后续的操作:
- 测试计划 (Test Plan): 所有测试元素的顶层容器,描述了测试的整体流程和配置。
- 线程组 (Thread Group): 定义虚拟用户(线程)数量、并发策略(Ramp-Up 时间)和循环次数。每个线程模拟一个真实用户。
- 取样器 (Sampler): 实际发送请求的组件,如 HTTP 请求、FTP 请求等。
- 逻辑控制器 (Logic Controller): 控制取样器的执行顺序和逻辑,如 If 控制器、循环控制器等。
- 配置元件 (Config Element): 为取样器提供配置支持,如 HTTP 请求默认值、HTTP Cookie 管理器、CSV 数据文件设置等。
- 断言 (Assertion): 验证服务器响应是否符合预期,如响应断言、JSON 断言等。
- 监听器 (Listener): 收集并展示测试结果,如查看结果树、聚合报告等。
- 前置处理器 (Pre Processor) / 后置处理器 (Post Processor): 在取样器发送请求前/后执行的操作,如参数提取、数据准备等(例如 JSON 提取器、BeanShell 后置处理器)。
- 定时器 (Timer): 控制请求的发送时机,如固定定时器、高斯随机定时器、同步定时器等。
二、并发测试实战:多步骤考试预约接口
我们将以一个“考试预约”的场景为例,该场景通常包含以下步骤:
- 用户登录,获取认证 Token。
- 获取考试列表信息,可能需要 Token。
- 根据选择的考试,获取该考试的场次信息,可能需要 Token 和考试 ID。
- 用户选择场次进行预约,需要 Token、考试 ID 和场次 ID。
我们将模拟多用户并发执行此流程。
三、JMeter 测试计划配置详解
(一)创建测试计划与线程组
启动 JMeter,默认会有一个空的测试计划。
右键点击【测试计划】 -> 【添加】 -> 【线程(用户)】 -> 【线程组】。
线程组核心参数配置说明:
- 线程数(并发用户数): 模拟的并发用户数量。例如,200 表示模拟 200 个用户。
- Ramp-Up 时间 (秒): 所有线程在多长时间内全部启动。例如,线程数 200,Ramp-Up 时间 10 秒,则表示 JMeter 会在 10 秒内逐步启动这 200 个线程,平均每秒启动 20 个。
- 循环次数: 每个线程执行测试逻辑的次数。若勾选“永远”,则持续执行直到手动停止。总请求数(不考虑逻辑控制器)约等于:
线程数 * 循环次数 * 每个循环内的请求数
。
(本教程后续会在(十二)处详细配置线程组参数,此处先创建)
(二)配置通用 HTTP 设置
为了简化后续 HTTP 请求的配置,我们可以添加一些全局配置元件。
添加 HTTP 请求默认值 (HTTP Request Defaults)
- 右键点击【线程组】 -> 【添加】 -> 【配置元件】 -> 【HTTP 请求默认值】。
- 配置说明:
- 协议:
http
或https
。 - 服务器名称或 IP: 被测服务器的地址,例如
api.example.com
。 - 端口号: 服务器端口,例如
80
或443
。 - 内容编码: 通常为
UTF-8
。 - 如果所有或大部分接口都访问同一服务器,配置此项能极大简化后续 HTTP 请求的配置。
- 协议:
- 右键点击【线程组】 -> 【添加】 -> 【配置元件】 -> 【HTTP 请求默认值】。
添加 HTTP 信息头管理器 (HTTP Header Manager)
- 右键点击【线程组】 -> 【添加】 -> 【配置元件】 -> 【HTTP 信息头管理器】。
- 配置说明:
- 用于添加所有 HTTP 请求共有的请求头,如
Content-Type: application/json
。 - 如果某些请求头的值是动态的(例如认证 Token),我们可以先定义一个通用的,后续再针对特定请求覆盖或添加。变量可以使用
${变量名}
的形式。
- 用于添加所有 HTTP 请求共有的请求头,如
(上图示例中的Authorization
头通常在登录后动态获取,此处仅为演示添加固定头的用法。在实际测试中,登录接口本身不需要Authorization
,而后续接口会动态使用登录后获取的 Token。)- 右键点击【线程组】 -> 【添加】 -> 【配置元件】 -> 【HTTP 信息头管理器】。
(三)步骤1:实现登录接口请求
添加 HTTP 请求 - 登录接口
- 右键点击【线程组】 -> 【添加】 -> 【取样器】 -> 【HTTP 请求】。将其命名为“登录请求”。
- 配置“登录请求”:
- 黄色区域: 如果已配置【HTTP 请求默认值】,且此接口与默认值一致(如服务器名、端口),则此处可留空。
- 红色部分:
- 方法: 选择请求方法,如
POST
。 - 路径: 接口的路径,例如
/api/auth/login
。 - 内容编码: 若与默认值不同,可单独指定。
- 方法: 选择请求方法,如
- 消息体数据 (Body Data) 或参数 (Parameters):
- 根据接口要求,如果是
POST/PUT
等请求,通常在“消息体数据”中填写 JSON、XML 等格式的请求体。 - 如果是
GET
或POST
表单,可以在“参数”中添加键值对。 - 我们将使用 CSV 文件参数化登录账号和密码,因此这里可以使用变量
${username}
和${password}
。
- 根据接口要求,如果是
- 右键点击【线程组】 -> 【添加】 -> 【取样器】 -> 【HTTP 请求】。将其命名为“登录请求”。
参数化登录数据 - 添加 CSV 数据文件设置 (CSV Data Set Config)
- 为了模拟不同用户登录,我们需要将账号密码参数化。
- 右键点击【登录请求】(或【线程组】) -> 【添加】 -> 【配置元件】 -> 【CSV 数据文件设置】。
- 配置 CSV 数据文件设置:
- 文件名: CSV 文件的绝对或相对路径(相对于 .jmx 文件)。例如
users.csv
。- CSV 文件内容示例 (users.csv):
1
2
3username,password
user1,pass1
user2,pass2
- CSV 文件内容示例 (users.csv):
- 文件编码: 根据 CSV 文件实际编码填写,常用
UTF-8
。 - 变量名称: 对应 CSV 文件首行的列名,用逗号分隔。例如
username,password
。 - 忽略首行 (Ignore first line): 如果 CSV 首行是标题,设为
True
。 - 分隔符: 默认为逗号
,
。 - 是否允许带引号 (Allow quoted data?): 如果数据中包含引号,设为
True
。 - 遇到文件结束符再次循环 (Recycle on EOF?):
True
表示读取完文件后从头开始,False
表示停止。 - 遇到文件结束符停止线程 (Stop thread on EOF?):
True
表示读取完文件后该线程停止执行。 - 线程共享模式 (Sharing mode):
- All threads (所有线程): 所有线程共享同一个 CSV 文件,每个线程依次取下一行数据。
- Current thread group (当前线程组): 仅当前线程组内的线程共享。
- Current thread (当前线程): 每个线程独立打开和读取文件(较少使用)。
- Edit: 自定义标识符,用于跨线程组共享。
- 文件名: CSV 文件的绝对或相对路径(相对于 .jmx 文件)。例如
验证登录结果 - 添加响应断言 (Response Assertion)
- 右键点击【登录请求】 -> 【添加】 -> 【断言】 -> 【响应断言】。
- 配置响应断言:
- Apply to: 通常是
Main sample only
。 - 测试字段 (Field to Test):
- 响应文本 (Text Response): 服务器返回的完整响应体。
- 响应代码 (Response Code): HTTP 状态码,如 200, 401。
- 响应信息 (Response Message): HTTP 状态消息,如 “OK”, “Unauthorized”。
- 响应头 (Response Headers): 检查响应头中的特定内容。
- 注意:响应文本不等于响应信息。如下图,响应数据有值,但响应信息为空,若选【响应信息】断言可能失败。
- 模式匹配规则 (Pattern Matching Rules):
- 包括 (Contains): 响应字段包含指定的字符串或正则表达式。支持正则。
- 匹配 (Matches): 响应字段完全匹配指定的正则表达式。
- 相等 (Equals): 响应字段与指定字符串完全相等(大小写敏感)。
- 字符串 (Substring): 响应字段包含指定的字符串。不支持正则,等效于“包括”不使用正则的情况。
- 否 (Not): 反转断言结果。
- 或者 (Or): 当有多个断言模式时,任一匹配即成功。
- 要测试的模式 (Patterns to Test): 点击“添加”输入期望的值。例如,对于登录成功,响应文本中可能包含
"success":true
或"code":200
。 - 自定义失败信息 (Custom failure message): 断言失败时显示的自定义消息。
- Apply to: 通常是
- 右键点击【登录请求】 -> 【添加】 -> 【断言】 -> 【响应断言】。
提取动态数据 - 添加 JSON 提取器 (JSON Extractor)
- 登录成功后,服务器通常会返回一个认证 Token,后续接口需要携带此 Token。我们需要从登录响应中提取它。
- 右键点击【登录请求】 -> 【添加】 -> 【后置处理器】 -> 【JSON 提取器】。
- 配置 JSON 提取器:
- Apply to: 通常是
Main sample only
。 - Names of created variables: 提取出的值要赋给的变量名,例如
authToken
。 - JSONPath expressions: 用于定位 JSON 数据中目标值的 JSONPath 表达式。
- JSONPath 语法简介:
$
:根对象。.
:子操作符。[]
:子操作符或数组索引。*
:通配符,匹配所有元素。..
:递归下降,搜索所有子孙节点。[?(expression)]
:过滤表达式。- 例如,如果响应是
{"data": {"token": "xyz123"}, "code": 0}
,提取 token 的表达式可以是$.data.token
。 - (为了确定正确的 JSONPath,可以先添加一个【查看结果树】监听器,运行一次请求,观察响应数据结构。)
(上图显示了响应结构,假设 token 在data.tokenValue
)
- JSONPath 语法简介:
- Match No. (0 for random):
0
:随机匹配一个(当表达式匹配到多个值时)。n
(正整数):匹配第 n 个。-1
:匹配所有,并将它们存储为varName_1
,varName_2
, …, 同时创建varName_matchNr
存储匹配总数。- 如果确定只匹配一个值,通常填
1
。
- Default Values: 当 JSONPath 表达式未匹配到任何值时使用的默认值。例如
TOKEN_NOT_FOUND
。 - Compute concatenation var (suffix _ALL): 如果勾选,并且 Match No. 为 -1,会将所有匹配到的值用逗号连接起来存入名为
N_ALL
的变量。
- Apply to: 通常是
(实际配置,假设 token 路径为$.data.tokenValue
)深入了解 JSONPath:
(可选) 变量作用域提升 - 添加 BeanShell PostProcessor
- JMeter 中,一个线程组内定义的变量(如通过 JSON 提取器创建的)默认在该线程组内可见。如果需要在不同线程组之间共享变量(较少见,通常推荐参数化或属性),或者想通过
__P
函数在测试计划级别使用,可以将其提升为 JMeter 属性。 - 右键点击【登录请求】 -> 【添加】 -> 【后置处理器】 -> 【BeanShell PostProcessor】。
- 配置 BeanShell PostProcessor:
- 在 Script区域输入:在后续请求中,可以通过
1
2
3
4
5// 将名为 authToken 的 JMeter 变量转换为全局属性 authTokenProp
props.put("authTokenProp", vars.get("authToken"));
// 或者使用 JMeter 内置函数 (更推荐)
// ${__setProperty(globalToken, ${authToken}, )}; // 注意有两个下划线${__P(authTokenProp)}
或${__property(authTokenProp)}
来使用这个全局属性。
对于本教程的单线程组场景,直接使用${authToken}
即可,无需提升。此处仅为演示该功能。
- 在 Script区域输入:
(图中示例使用了__setProperty
函数,token
是 JSON 提取器中定义的变量名,token
(第一个参数) 是新设置的属性名。建议属性名和变量名有所区分以避免混淆,如globalAuthToken
。)- JMeter 中,一个线程组内定义的变量(如通过 JSON 提取器创建的)默认在该线程组内可见。如果需要在不同线程组之间共享变量(较少见,通常推荐参数化或属性),或者想通过
(四)步骤2:实现获取考试信息接口
添加 HTTP 请求 - 获取考试信息
- 右键点击【线程组】 -> 【添加】 -> 【取样器】 -> 【HTTP 请求】。命名为“获取考试信息”。
- 配置:
- 方法: 例如
GET
。 - 路径: 例如
/api/exams
。 - 请求头: 如果此接口需要认证,需添加
Authorization
头。在此请求下右键添加【HTTP 信息头管理器】,并添加Authorization
头,其值为Bearer ${authToken}
(假设 Token 类型是 Bearer,authToken
是上一步提取的变量)。- 如果已在线程组级别添加了通用的信息头管理器(如
Content-Type
),这里添加的会与全局的合并或覆盖同名头。
- 如果已在线程组级别添加了通用的信息头管理器(如
- 方法: 例如
(上图中,绿色区域演示了如何在请求参数中直接使用提取的 Token,但更规范的做法是通过 HTTP 信息头管理器添加 Authorization Header。)提取考试 ID - 添加 JSON 提取器
- 假设此接口返回考试列表,我们需要提取其中一个考试的 ID (
examId
) 用于下一步。 - 右键点击【获取考试信息】 -> 【添加】 -> 【后置处理器】 -> 【JSON 提取器】。
- 配置:
- Names of created variables:
examId
- JSONPath expressions: 例如
$.data[0].id
(提取列表第一个考试的 ID) - Match No.:
1
- Default Values:
EXAM_ID_NOT_FOUND
- Names of created variables:
- 假设此接口返回考试列表,我们需要提取其中一个考试的 ID (
(五)步骤3:实现获取场次信息接口
添加 HTTP 请求 - 获取场次信息
- 右键点击【线程组】 -> 【添加】 -> 【取样器】 -> 【HTTP 请求】。命名为“获取场次信息”。
- 配置:
- 方法: 例如
GET
。 - 路径: 可能是
/api/exams/${examId}/sessions
(注意路径中使用了上一步提取的examId
变量)。 - 请求头: 同样,如果需要认证,添加
Authorization: Bearer ${authToken}
。
- 方法: 例如
提取场次 ID - 添加 JSON 提取器
- 从此接口响应中提取场次 ID (
sessionId
)。 - 右键点击【获取场次信息】 -> 【添加】 -> 【后置处理器】 -> 【JSON 提取器】。
- 配置:
- Names of created variables:
sessionId
- JSONPath expressions: 例如
$.data.sessions[0].sessionId
- Match No.:
1
- Default Values:
SESSION_ID_NOT_FOUND
- Names of created variables:
- 从此接口响应中提取场次 ID (
(六)步骤4:实现考试预约接口(关键并发点)
这是我们重点关注并发性能的接口。
添加 HTTP 请求 - 考试预约
- 右键点击【线程组】 -> 【添加】 -> 【取样器】 -> 【HTTP 请求】。命名为“考试预约”。
- 配置:
- 方法: 例如
POST
。 - 路径: 例如
/api/reservations
。 - 消息体数据:
1
2
3
4
5{
"examId": "${examId}",
"sessionId": "${sessionId}",
"userId": "${username}" // 假设预约也需要用户信息, username 来自 CSV
} - 请求头: 此请求通常需要特定的
Content-Type
(如application/json
) 和Authorization
。可以在此 HTTP 请求下右键添加一个【HTTP 信息头管理器】进行配置。
- 方法: 例如
实现精确并发 - 添加同步定时器 (Synchronizing Timer)
- 为了模拟大量用户在同一时刻(或极短时间内)同时发起“考试预约”请求,我们需要使用同步定时器。它会阻塞线程,直到达到设定的线程数量后,再同时释放这些线程。
- 右键点击【考试预约】HTTP 请求 -> 【添加】 -> 【定时器】 -> 【Synchronizing Timer】。
- 配置同步定时器:
- 模拟用户组的数量 (Number of Simulated Users to Group by): 每次集结多少个线程后同时释放。
- 如果设为
0
,则等同于线程组中定义的总线程数。 - 如果设为
10
,则 JMeter 会等待,直到有 10 个线程到达此步骤,然后同时释放它们。此值不能超过线程组的总线程数。
- 如果设为
- 超时时间以毫秒为单位 (Timeout in milliseconds):
- 如果设为
0
,Timer 将一直等待,直到线程数达到“模拟用户组的数量”。 - 如果大于
0
,例如5000
(5秒),若 5 秒内未集齐指定数量的线程,Timer 将不再等待,直接释放已到达的线程。
- 如果设为
- 模拟用户组的数量 (Number of Simulated Users to Group by): 每次集结多少个线程后同时释放。
- 重要提示: 同步定时器应仅添加在需要精确并发控制的请求上,而不是所有请求。
(七)添加监听器以查看和分析结果
查看结果树 (View Results Tree)
- 用于调试和查看单个请求的详细信息(请求、响应数据、响应头等)。在高并发测试时,建议禁用或仅在调试少量用户时启用,因为它非常耗费资源。
- 右键点击【线程组】 -> 【添加】 -> 【监听器】 -> 【查看结果树】。
- (之前在讲解 JSON 提取器时已提及,用于观察响应结构。)
聚合报告 (Aggregate Report)
- 提供关键性能指标的汇总统计,是性能分析的主要依据。
- 右键点击【线程组】 -> 【添加】 -> 【监听器】 -> 【聚合报告】。
- 聚合报告参数解读:
- Label: JMeter 元件的名称(如 HTTP 请求的名称)。
- # Samples (样本数): 发出的总请求数量。
- Average (平均值): 平均响应时间 (毫秒)。
- Median (中位数): 50% 用户的响应时间小于此值。
- 90% Line (90% 百分位): 90% 用户的响应时间小于此值。
- 95% Line (95% 百分位): 95% 用户的响应时间小于此值。
- 99% Line (99% 百分位): 99% 用户的响应时间小于此值。
- Min (最小值): 最小响应时间。
- Max (最大值): 最大响应时间。
- Error % (异常%): 错误请求的百分比。出现错误时需结合服务端日志排查。
- Throughput (吞吐量): 通常指 TPS (Transactions Per Second),即服务器每秒处理的请求数。越高代表服务器处理能力越强。
- Received KB/sec (接收): 每秒从服务器接收的数据量 (KB)。
- Sent KB/sec (发送): 每秒向服务器发送的数据量 (KB)。
(可选) 用表格查看结果 (View Results in Table)
- 以表格形式展示每个请求的结果,比聚合报告更细致,但同样消耗资源,高并发时慎用。
(八)最终配置线程组参数
回到我们之前创建的【线程组】,现在根据测试目标配置并发参数。
- 参考本教程(一)中对线程组参数的解释。
- 示例配置:
- 线程数: 200
- Ramp-Up 时间: 10 (秒) -> 每秒启动20个用户
- 循环次数: 10 -> 每个用户执行10次完整流程
四、执行测试并生成 HTML 报告
保存测试计划: 点击菜单栏 文件 -> 保存测试计划为… (例如
exam_booking_test.jmx
)。运行测试: 点击工具栏的绿色启动按钮。
在聚合报告中配置结果文件(用于生成 HTML 报告):
- 在【聚合报告】的“文件名 (Filename)”字段,浏览并指定一个路径和文件名,用于保存原始结果数据,后缀为
.jtl
或.csv
。例如D:\JMeter_Reports\exam_test_results.jtl
。 - 重要: 最好在执行测试前配置好此项,这样 JMeter 会在运行时自动将数据写入文件。如果测试已运行完毕,也可以手动点击聚合报告中的“保存表格数据”按钮。
- 在【聚合报告】的“文件名 (Filename)”字段,浏览并指定一个路径和文件名,用于保存原始结果数据,后缀为
生成 HTML 报告 (推荐在 GUI 模式测试完成后或非 GUI 模式下使用):
JMeter 提供了一个非常美观和详细的 HTML 动态报告。
方式一:通过 JMeter GUI 生成
- 测试运行完毕后,点击菜单栏【工具 (Tools)】 -> 【Generate HTML report】。
- 在弹出的对话框中:
- Results file (CSV or JTL): 选择上一步保存的
.jtl
或.csv
文件。 - user.properties file: 指向 JMeter
bin
目录下的user.properties
文件。 - Output directory: 指定一个空的文件夹用于存放生成的 HTML 报告。
- Results file (CSV or JTL): 选择上一步保存的
- 点击【Generate report】按钮。成功后会提示。
- 打开指定的输出目录,找到
index.html
并用浏览器打开即可查看报告。
- 测试运行完毕后,点击菜单栏【工具 (Tools)】 -> 【Generate HTML report】。
方式二:通过命令行生成 (更推荐用于自动化和大规模测试)
1
2# 切换到 JMeter 的 bin 目录执行
jmeter -g /path/to/your/results.jtl -o /path/to/your/empty_html_report_folder-g
: 指定 JTL 结果文件。-o
: 指定 HTML 报告输出的空文件夹。
五、JMeter 性能测试最佳实践与技巧
使用非 GUI 模式进行压力测试: JMeter GUI 模式消耗较多资源,仅适用于脚本开发和调试。进行正式的压力测试时,务必使用命令行(非 GUI)模式:
1
jmeter -n -t /path/to/your/test_plan.jmx -l /path/to/your/results.jtl -e -o /path/to/your/empty_html_report_folder
-n
: 非 GUI 模式-t
: 指定 JMX 测试计划文件-l
: 指定 JTL 结果日志文件-e
: 测试结束后生成 HTML 报告-o
: HTML 报告输出目录 (必须为空)
合理命名元件: 为你的测试计划、线程组、取样器等元件取有意义的名称,方便调试和结果分析。
参数化数据: 使用 CSV Data Set Config 等方式参数化测试数据,使测试更接近真实场景。
使用断言: 确保你的请求得到了预期的响应,而不仅仅是 HTTP 200。
谨慎使用监听器: 【查看结果树】、【用表格查看结果】等详细监听器在GUI模式下非常耗费内存和 CPU。在正式压测时,应禁用它们,仅保留【聚合报告】或【Summary Report】并将结果写入文件。
思考时间 (Think Time): 使用定时器(如高斯随机定时器、固定吞吐量定时器)模拟用户的自然停顿,使测试更真实。同步定时器是特定场景下的并发控制。
关联 (Correlation): 熟练使用后置处理器(如 JSON 提取器、正则表达式提取器)提取动态数据,用于后续请求。
模块化与复用: 对于公共的测试片段(如登录流程),可以使用【测试片段 (Test Fragment)】和【模块控制器 (Module Controller)】或【Include Controller】来实现复用。
逐步增加负载: 不要一开始就用最大并发数,应逐步增加负载,观察系统瓶颈。
分布式测试: 当单台测试机无法产生足够负载时,可使用 JMeter 的分布式测试功能(Master-Slave 模式)。
六、总结
通过本文的介绍,您应该对如何使用 JMeter 进行一个涉及多步骤、数据依赖的 API 并发测试有了清晰的认识。从基础的线程组、HTTP 请求配置,到参数化、动态数据提取、响应断言,再到关键的同步定时器应用和专业的聚合报告解读、HTML 报告生成,这些都是构建有效性能测试场景的核心要素。
JMeter 的功能远不止于此,希望本文能为您打开一扇门,鼓励您在实际工作中不断探索和应用,以保障您所开发的 Web 应用程序的高效与可扩展性。作为后端开发人员,掌握 JMeter 这类工具,对于验证系统性能、发现瓶颈、推动产品质量提升具有不可替代的作用。祝您测试愉快!
JMeter 实战指南:从入门到复杂场景并发测试