4. 测试脚本开发:JMeter脚本编写、参数化配置、断言设置、监听器使用

脚本开发是吞吐测试的“手艺活”。工具摆在那,但能不能压出真实数据,全看脚本写得细不细。我见过太多人把JMeter当“点一下就跑”的工具,结果压出来的曲线跟业务实际差了十万八千里。

这一章,咱们把脚本开发的四个核心环节拆开揉碎。你跟着走一遍,基本就能应付90%的吞吐测试场景了。

4.1 脚本编写:从录制到手工构建

JMeter脚本怎么来?无非两条路:录,或者写。

录制适合快速上手。用Badboy或者JMeter自带的HTTP(S) Test Script Recorder,浏览器上点一圈,脚本就出来了。但我个人习惯是:录完一定得手工清理。录制脚本里全是静态资源的请求——图片、CSS、JS,这些在吞吐测试里通常要过滤掉。你想想看,压测目标是测接口吞吐,不是测CDN。

手工构建才是正经做法。我建议你直接建一个空的测试计划,然后按业务逻辑一步步加Sampler。核心步骤就这几步:

  1. 右键测试计划 → Add → Threads (Users) → Thread Group
  2. 右键Thread Group → Add → Config Element → HTTP Request Defaults(填协议、域名、端口)
  3. 右键Thread Group → Add → Sampler → HTTP Request(填路径、方法、参数)
  4. 右键Thread Group → Add → Listener → View Results Tree(调试用)

举个例子,一个简单的登录接口脚本:

Thread Group
├── HTTP Request Defaults
│   ├── Protocol: https
│   ├── Server Name: api.example.com
│   └── Port: 443
├── HTTP Request
│   ├── Method: POST
│   ├── Path: /v1/login
│   ├── Parameters:
│   │   ├── username: test_user
│   │   └── password: 123456
└── View Results Tree

嗯,这里要注意:别把密码硬编码在脚本里。后面我们会用参数化来解决。

我的习惯: 每个HTTP Request都加一个注释(右键 → Add Note),写上这个请求的业务含义。脚本一多,你回头看的时候就知道当初为什么这么写了。

4.2 参数化配置:让脚本“活”起来

吞吐测试最忌讳什么?所有用户用同一套数据。数据库里唯一索引一冲突,压测全废。参数化就是让每个虚拟用户用不同的数据去请求。

JMeter里参数化有四种常用方式,我按推荐度排个序:

方式 适用场景 数据量 我踩过的坑
CSV Data Set Config 大量用户、不同账号 百万级 文件路径写相对路径,换机器就找不到
User Defined Variables 少量固定参数(如域名) 几个 线程间共享,别用来做用户隔离
函数助手(__Random等) 随机数值、时间戳 无限 随机范围太大,数据分布不均匀
JDBC PreProcessor 从数据库实时取数据 动态 连接池耗尽,压测变数据库压测

CSV Data Set Config 是我最常用的。配置起来很简单:

Filename: /data/users.csv
Variable Names: username,password,token
Delimiter: ,
Recycle on EOF: False
Stop thread on EOF: True

然后你在HTTP Request的参数里写 ${username}、${password} 就行了。每个线程取一行,用完即止。

我曾经犯过的错: 有一次压测线上环境,CSV文件里放了100万条真实手机号。结果Recycle on EOF设成了True,数据循环使用,同一个手机号被多个线程同时登录,触发了风控。从那以后,我所有压测数据都用脱敏的,而且Recycle一定设False。

4.3 断言设置:别让错误数据污染结果

断言是什么?说白了就是“检查服务器返回的东西对不对”。没有断言,你看到的TPS可能是99%的报错请求堆出来的。我见过一个项目,TPS跑了5000,结果断言一加,实际成功的只有200。

JMeter里常用的断言有这几种:

  • Response Assertion:检查响应文本是否包含某个字符串。最常用,比如检查返回里有没有 "code":0。
  • Duration Assertion:检查响应时间是否超过阈值。超过就算失败,适合做超时监控。
  • Size Assertion:检查响应体大小。有时候服务器返回空数据也算成功,但业务上其实是失败的。
  • JSON Assertion:专门解析JSON响应,检查某个字段的值。比Response Assertion更精确。

配置示例(Response Assertion):

Apply to: Main sample only
Test Field: Response Text
Pattern Matching Rules: Contains
Patterns to Test:
  "status": "success"

我个人习惯是:每个Sampler至少加一个断言。哪怕只是检查HTTP 200,也比不检查强。你想想看,如果服务器挂了返回502,你的TPS曲线还是一条直线,那数据还有什么意义?

核心原则: 断言失败 = 请求失败。在聚合报告里,断言失败的请求会计入Error%。所以断言越严格,你的吞吐数据越真实。

4.4 监听器使用:看懂数据才是关键

监听器是JMeter的“仪表盘”。但很多人一上来就加十几个监听器,结果跑完一看,该看的数据一个没看对。

我常用的监听器就三个,足够了:

监听器 用途 什么时候用
View Results Tree 调试脚本,看单个请求的请求/响应详情 脚本开发阶段
Summary Report 查看吞吐量、平均响应时间、错误率 压测执行阶段
Aggregate Report 按标签分组统计,看每个接口的详细指标 结果分析阶段

View Results Tree 只在调试时打开。正式压测一定要关掉,否则它会吃掉大量内存,影响压测结果。我记得有一次压测,开着View Results Tree跑了10分钟,结果JMeter直接OOM了。

Summary Report 是压测时的主力。它实时显示:

  • #Samples:总请求数
  • Average:平均响应时间(ms)
  • Min/Max:最小/最大响应时间
  • Std. Dev.:标准差(响应时间波动越大,值越大)
  • Error %:错误率
  • Throughput:吞吐量(req/s)
  • Received KB/sec:接收速率
  • Sent KB/sec:发送速率

嗯,这里要注意:Throughput才是你真正要盯的指标。Average响应时间只是参考,别被它牵着走。

我的建议: 压测结束后,把Summary Report保存为CSV文件。然后用Excel或者Python画图,比JMeter自带的图表直观得多。我一般会画三张图:TPS曲线、响应时间分布、错误率变化。这三张图一出来,系统瓶颈在哪基本就清楚了。

4.5 脚本开发全流程:一张图看懂

说了这么多,咱们用一张SVG图把整个流程串起来。从创建测试计划到最终输出报告,每一步都离不开前面讲的内容。

JMeter脚本开发全流程 1. 创建测试计划 2. 配置线程组 3. 添加HTTP Sampler 4. 参数化配置 5. 设置断言 6. 添加监听器 7. 运行并分析报告 关键提醒 • 参数化是脚本“活”起来的核心 • 断言过滤无效请求 • 监听器只开必要的 避坑指南 • 调试完关掉View Results Tree • CSV文件路径用绝对路径

这张图把脚本开发的七个步骤串起来了。你每次写脚本的时候,按这个流程走一遍,基本不会漏东西。我个人习惯是:每写完一个Sampler,立刻加断言和参数化,最后再加监听器。这样调试的时候就能看到真实数据,不用回头补。

4.6 实战小贴士:脚本调试三板斧

脚本写完了,怎么知道对不对?我一般用这三招:

  1. 单线程跑一次:线程数设1,循环次数设1,打开View Results Tree看请求和响应。这一步能发现90%的配置错误。
  2. 加Debug Sampler:在参数化之后加一个Debug Sampler,可以打印出所有变量的值。看看${username}是不是真的取到了数据。
  3. 检查断言日志:如果断言失败,JMeter会在日志里写清楚失败原因。别只看“失败”两个字,点开看看具体是哪个字段没匹配上。
我曾经踩过的坑: 有一次脚本跑了一晚上,第二天发现所有请求都成功了,但TPS只有100。排查了半天,发现是参数化文件里的数据全是重复的,每个线程都在请求同一个资源。从那以后,我每次压测前都会先跑一个短时间的预压,看看数据分布是否均匀。

脚本开发这块,说白了就是“细心活”。参数化、断言、监听器,每一个环节都直接影响最终数据的可信度。你按这个流程走,脚本质量不会差。