☰
全国大学生软件测试大赛Web性能测试完整复盘:JMeter从负载建模到报告
2026/9/30 1:13:31 网站建设 项目流程

第一次拿到 Web 性能测试赛项的题目,我盯着那张只有几行需求的纸坐了大概十分钟,脑子里想的不是"怎么压",而是"它到底想让我交出什么东西"。全国大学生软件测试大赛里的 Web 性能测试方向,和平时做功能测试、写几个用例跑一跑的感觉完全不一样——它考的是你能不能把一套负载模型搭起来、把数据读明白、把结论说清楚。这篇就当是我自己的一次完整复盘,从环境准备、脚本开发、场景设计,一直写到结果分析和报告收尾,重点放在"一个完整实例"上,而不是零散的知识点罗列。不管你是第一次报名、还在纠结 JMeter 装哪个版本,还是已经能跑通脚本但报告写不出结论,我想这些内容都能对上你的需求。软件测试这个行当里,性能测试一直是面试高频区,八股文背得再熟,真到压一个系统的时候能不能讲清楚"并发数怎么算出来的""TPS 掉在哪个环节",才是分水岭。

1. 赛项拆解:Web 性能测试到底在考什么

1.1 从任务书倒推考点分布

我习惯拿到题目先做一件事:把任务书里的动词圈出来。通常会出现"设计""执行""分析""提交"这几个词,它们其实对应了四个完全不同的评分维度。设计对应测试计划的结构是否合理,执行对应脚本能否稳定跑完、数据是否可信,分析对应你能不能从聚合报告里挑出真正的瓶颈,提交对应报告有没有把结论讲清楚。

很多同学栽在"执行"上,觉得脚本能跑通就万事大吉,结果跑到一半冒出几百个错误,最后交上去的数据自己都不敢信。也有人脚本写得漂亮,报告里只有一句"系统性能良好",评委看完完全不知道他测了什么。所以真正的考点不是工具会不会用,而是闭环能力:需求到模型,模型到脚本,脚本到数据,数据到结论。

还有一点容易被忽略:题目里给的业务场景通常只有一两个页面,比如登录加查询,看起来特别简单。但越是简单的场景,越考验你对参数化、关联、事务边界的处理是否规范。因为场景简单,评委的注意力就全落在细节上。

1.2 为什么 JMeter 是事实标准

这几年赛项里能用的工具其实不止一个,LoadRunner、Locust、k6 都有人用,但 JMeter 依然是绝大多数人的选择,原因很实在。第一,它是纯 Java 的,跨平台,Windows 和 Linux 上跑起来行为一致;第二,命令行模式加 HTML 报告的组合,天然适合"跑完就出报告"的比赛节奏;第三,插件生态成熟,阶梯加压、每秒事务数曲线这些都有现成组件。

我用 Locust 也做过几轮压测,Python 写脚本确实舒服,但在比赛这种时间紧、要反复调参的场景下,JMeter 的图形化元件树对排查问题的帮助更大——你能一眼看出哪个元件顺序放错了。Locust 一旦脚本里有个协程阻塞,排查起来就很费劲。

注意:工具选型不要临时改。见过有人前两天用 JMeter 写了一版,第三天觉得 k6 更酷,结果两边都半吊子。选定一个就深挖到底。

顺带说一句,性能测试相关岗位的面试题里,"你为什么选 JMeter 而不是 LoadRunner"出现的频率非常高,答案不是"因为免费",而是要说清楚协议支持范围、扩展方式、报告能力这几个维度的取舍。这段话你写进比赛报告里,也是加分的。

2. 环境搭建与工程目录规划

2.1 版本匹配这件小事的杀伤力

JMeter 对 JDK 版本有要求,新版一般要求 JDK 8 以上,某些版本在 JDK 17 上会有模块访问告警。我踩过的坑是:本机装了 JDK 17,JMeter 启动正常,但一挂上某些老插件就报反射相关的异常。后来统一换成 JDK 8 或者 JDK 11,问题就消失了。

具体做法是给 JMeter 单独指定 JDK,不去动系统的 JAVA_HOME。在jmeter.bat同目录下改环境变量,或者直接在命令行里临时设置:

export JAVA_HOME=/opt/jdk-11 export PATH=$JAVA_HOME/bin:$PATH ./jmeter -v

Windows 下同理,用set JAVA_HOME=...临时覆盖。这样做的理由是避免污染系统环境,比赛机器往往是公用的,动全局变量容易出乱子。

内存参数也必须调。默认堆只有 1G,压测时如果开了聚合报告这种内存大户,很容易 OOM。在jmeter.bat或jmeter脚本里找到 HEAP 那一行,改成:

HEAP="-Xms2g -Xmx4g -XX:MaxMetaspaceSize=256m"

Xms 和 Xmx 设成一样,可以避免堆在运行中反复扩容带来的抖动,这个细节在长时间稳态压测里影响不小。

2.2 目录结构决定你能不能在 5 分钟内重跑

我见过太多人把所有文件丢在桌面,result.jtl和plan.jmx混在一起,第二次跑的时候覆盖了第一次的数据,急得拍桌子。一个干净的结构应该是这样:

目录用途说明
plan/存放.jmx脚本按场景命名,如login_query.jmx
data/CSV 参数文件账号、关键词等
result/jtl 与 HTML 报告每次执行建一个带时间戳的子目录
conf/自定义 properties覆盖jmeter.properties里的关键项

执行的时候用绝对路径或者-p指定配置文件,避免"在我机器上能跑"的尴尬。

jmeter.properties里我必改的几项,顺便解释下为什么:

jmeter.save.saveservice.output_format=csv jmeter.save.saveservice.thread_counts=true sampleresult.default.encoding=UTF-8 CookieManager.save.cookies=true httpsampler.max_redirects=20

thread_counts=true会在结果文件里记录当时的线程数,后面画 TPS 与并发的关系曲线就靠它,不打开的话你根本不知道某个时间点压了多少并发。CookieManager.save.cookies=true是把 Cookie 里的 JSESSIONID 之类当变量存下来,后面做关联判断时很方便。

提示:改 properties 之前先备份原文件。比赛现场如果要换机器,把conf/整个目录拷过去就能复现环境,比重新配一遍省十几分钟。

3. 脚本开发实战:从录制到参数化关联

3.1 录制还是手写,别纠结太久

录制能省时间,但录出来的脚本通常是一坨——一堆自动命名的事务控制器、一堆用不上的监听器、固定的 Cookie 和 token。我的做法是折中:用浏览器代理或者 BlazeMeter 插件录一遍,只保留请求本身,然后手工重建元件树。

重建的时候按这个顺序往下摆:

  1. 测试计划
  2. 线程组
  3. HTTP 请求默认值(填服务器、端口、协议、内容编码 UTF-8)
  4. HTTP Cookie 管理器
  5. HTTP 信息头管理器
  6. 事务控制器(包住一个完整业务)
  7. 取样器 + 提取器 + 断言

顺序不是随便排的。HTTP 请求默认值要放在线程组下面、取样器上面,这样同级的取样器才继承得到。Cookie 管理器和信息头管理器是并列关系,谁先谁后不影响,但都必须在取样器之前。

事务控制器有一个选项叫Generate parent sample,勾上之后,聚合报告里只显示事务控制器这一条的统计,子请求被折叠进去。这样做的好处是报告干净,你一眼能看出"一个完整的登录业务"平均耗时多少;坏处是你看不到单个请求的耗时,定位问题时要临时取消勾选。我在比赛里的做法是:调脚本阶段不勾,出报告阶段勾上。

3.2 参数化:CSV Data Set Config 的几个开关

参数化是为了让不同虚拟用户用不同数据,避免缓存命中导致数据虚高。CSV 数据文件集配置里,参数含义要搞清楚:

参数取值建议原因
Filename用相对路径data/accounts.csv换机器不用改路径
Variable Namesusername,password与请求体里的占位符对应
Delimiter逗号数据里别混入逗号
Recycle on EOF视情况数据够用就设 False
Stop thread on EOF视情况数据不够时避免线程空转
Sharing modeAll threads多线程下各取一行,不重复

Sharing mode这个选项特别容易忘,默认就是 All threads,如果误改成 Current thread,每个线程都会从头读文件,参数化就白做了。

还有一个隐藏坑:CSV 文件如果带 BOM 头,第一行的变量名会带着不可见字符,导致取值失败。用记事本另存的时候选"UTF-8 无 BOM",或者干脆用 VS Code 检查一遍。

3.3 关联:正则和 JSON 提取器怎么选

关联的本质是把上一个请求的响应内容取出来,喂给下一个请求。接口返回 JSON 就用 JSON 提取器,返回 HTML 或者混合文本才用正则提取器。选错的代价是表达式又长又容易断。

JSON 提取器的配置很直观:Names of created variables写变量名,JSON Path expressions写$.data.token这样的路径,Match No.填 0 或 1,Default Values一定要填一个兜底值。

注意:Default Values 千万别留空。留空的话,一旦提取失败,后续请求会带着空值发出去,服务端返回 401,你在聚合报告里看到的是大面积错误,但要排查半天才知道根因是提取器没配好。

正则提取器的模板务必写$1$,匹配数字写 1,默认值同样要填。

调试关联有个笨办法但很好用:加一个 Debug Sampler 加一个察看结果树,跑一次单线程,看看变量到底有没有取到。别一次开一百个线程去猜。

3.4 断言与事务边界的配合

断言是保证数据可信的底线。响应断言里,我一般同时检查响应码和一段关键文本。只检查响应码有个问题:有些系统出错时也返回 200,但页面内容是"系统繁忙"。只检查文本又可能因为文案微调而误报。两个一起上,稳。

持续时间断言也值得加,比如要求接口 500ms 内返回,就设Duration in milliseconds为 500。这样超时的请求会被标记为失败,你就能算出"响应时间达标率"这个指标,比单纯看平均值有说服力得多。

事务的边界要想清楚。我见过有人把一次页面加载拆成十几个请求,每个都单独算事务,报告里几十行数据,评委根本找不到重点。合理的做法是:一个完整的用户操作 = 一个事务。登录、查询列表、提交表单,各算一个。

4. 场景设计与负载模型

4.1 并发数、RPS、TPS 到底怎么换算

这是整个比赛里我最想强调的一段,因为太多人凭感觉填数字。三者关系用利特尔法则就能说清楚:

并发数 = TPS × 平均响应时间(秒)

举个例子。题目要求系统支撑每秒 100 笔业务,你测出来平均响应时间是 200 毫秒,那么需要维持的并发就是 100 × 0.2 = 20 个线程。如果响应时间涨到 800 毫秒,同样 100 TPS 就需要 80 个并发——这就是为什么响应时间一恶化,系统就雪崩。

反过来说,如果你填了 100 个线程,测出来 TPS 只有 50,平均响应时间 2 秒,套公式一算 100 × 2 = 200 ≠ 50,说明什么?说明有大量请求在排队或者失败,系统已经过载了。

把这段计算过程写进报告,比任何形容词都有分量。

4.2 阶梯加压与稳态保持

不要一上来就拉到最大并发。正确做法是阶梯加压,观察曲线拐点。用 Custom Thread Groups 插件里的 Stepping Thread Group,配置大概是:

  • 初始并发 10
  • 每 30 秒加 10 个线程
  • 加到 100 停止加压
  • 稳态保持 5 分钟
  • 然后线性降下来

为什么要保持至少 5 分钟?因为系统在头一两分钟里有缓存预热、连接池填充、JIT 编译这些过程,数据不稳定。稳态期的数据才是可信的。

降速阶段也别省,有些系统在压力撤掉之后会有资源回收的抖动,这属于观察项。

如果比赛环境不让装插件,就用普通线程组配合Ramp-up Period,比如 100 个线程、ramp-up 写 200 秒,也勉强能模拟线性加压,只是看不出稳态平台。

4.3 思考时间和吞吐量控制器

思考时间决定负载的真实性。真实用户在两秒内连点五次和每两秒点一次,给系统的压力完全不同。用 Constant Timer 或 Gaussian Random Timer 模拟,一般设在 1 到 3 秒之间。

如果题目直接给了目标吞吐量,那就用 Constant Throughput Timer。有个坑必须说:这个元件的单位是每分钟,不是每秒。要跑 120 TPS,得填 7200。我第一次用的时候填了 120,跑出来 2 TPS,还以为是系统扛不住,白白排查了半小时。

同时要注意Calculate Throughput based on这个选项,默认是this thread only,意思是每个线程各自限速,多线程下总吞吐会被放大。想做全局限速,得选all active threads。

5. 执行与结果分析:把数据读成结论

5.1 命令行执行与 HTML 报告

正式压测一定用命令行,GUI 模式只用来调脚本。GUI 开着跑压测,JMeter 自己的界面渲染会吃掉大量 CPU,数据完全不可信。而且启动时会弹那句经典提示。

命令大概长这样:

rm -rf result/run_01 ./jmeter -n \ -t plan/login_query.jmx \ -l result/run_01/result.jtl \ -e -o result/run_01/html \ -Jthreads=100 -Jduration=300

-J传的是自定义属性,在脚本里用${__P(threads)}引用,这样同一份脚本改参数就能跑不同场景,不用重新打开 GUI。

有个细节:-e -o生成 HTML 报告需要 jtl 里有足够的数据,数据量太少或者时间跨度太短(官方建议 30 秒以上)会直接报错不生成。所以哪怕只是试跑,也让它跑够一分钟。

5.2 聚合报告里真正该看的几列

聚合报告列很多,但重点就几个:

指标含义怎么用
Average平均响应时间容易被极值拉偏,参考即可
90% Line90% 请求的响应时间比平均值更能反映用户体验
99% Line长尾请求判断是否有慢请求拖后腿
Error %错误率超过 1% 就要警惕
Throughput吞吐量(默认按秒)与目标 TPS 对比
Received KB/sec网络吞吐判断是否受带宽限制

如果平均值和 90% 线差距巨大,比如平均 300ms 但 90% 线是 2s,说明有一小撮请求特别慢,可能是慢 SQL 或者锁竞争,这时候要去响应时间分布图里看。

5.3 从曲线里找拐点

用 TPS 图加响应时间图叠着看,你会看到一个典型的形态:并发增加,TPS 线性上升,响应时间基本平稳;过了某个点,TPS 不再涨甚至下降,响应时间开始陡增。这个点就是系统的最大处理能力。

我在一次练习里测出来,并发从 10 加到 60 的时候,TPS 从 50 稳定涨到 280,响应时间一直是 200ms 上下;到 80 并发时 TPS 反而掉到 260,响应时间跳到 600ms。结论就很明确:这台环境在 60 到 80 并发之间存在性能拐点,推荐工作负载不超过 60 并发。

这个结论比"系统性能良好"有用一百倍。

6. 常见问题排查与独家避坑清单

6.1 中文乱码与编码

乱码的根源永远是编码不一致。按这个顺序检查:CSV 文件是不是 UTF-8 无 BOM;HTTP 请求默认值里的Content encoding有没有写 UTF-8;jmeter.properties里sampleresult.default.encoding是不是 UTF-8。三处都对上,基本就不乱码了。

还有一种乱码只在察看结果树里出现,但断言能过,那通常是响应头的 Content-Type 没带 charset,JMeter 按默认编码解析了。这种情况不影响压测结果,可以忽略,但如果要写进报告最好说明一下。

6.2 连接类报错速查

报错信息常见原因处理方式
ConnectException目标端口未监听或防火墙拦截先用 curl 从压测机验证连通性
SocketException: Too many open files文件描述符耗尽调大ulimit -n,同步调优内核参数
BindException: Address already in use本地端口耗尽缩短连接复用时间,或开启端口复用
Read timed out服务端处理超时确认超时阈值,区分是网络还是业务慢
Non HTTP response code非 HTTP 协议返回检查是否被重定向到登录页

最后一行那个特别常见:压测跑着跑着,一部分请求被重定向到登录页,返回的是 HTML 而不是 JSON。原因是会话过期或者 Cookie 没保持住。解决办法是把登录做成一次性前置,用setUp Thread Group单独跑,把 token 提取到全局属性里,后面所有线程引用同一个 token。

6.3 压测机自己成为瓶颈的识别

这是最阴的一类问题:你以为是服务端不行,其实是压测机扛不住。判断方法看两个地方。

一是看压测机的 CPU 和内存。如果压测过程中压测机 CPU 跑到 90% 以上,那数据基本不可信。二是看错误类型。如果错误集中在连接建立阶段(ConnectException、BindException),且数量随并发线性增长,八成是压测机的端口或者文件句柄到上限了。

我遇到过最典型的一次:800 并发的时候错误率突然跳到 15%,怎么调服务端参数都没用。后来在压测机上执行ulimit -n,显示 1024。改成 65535 之后,错误率直接归零。这个坑我记了很久。

提示:分布式压测能缓解压测机瓶颈,但会引入时钟同步、结果合并的问题。比赛时间紧的话,先把单机调明白。

7. 测试报告撰写与提分细节

7.1 一份能被评委读完的报告骨架

报告不用写得像论文,但要有一条清晰的主线。我的结构是:测试目标与范围、测试环境(含压测机与被测机配置)、业务模型与负载模型(含并发数推算过程)、测试执行记录、结果数据与分析、结论与建议、附录(脚本与原始数据)。

其中"并发数推算过程"和"结论与建议"是拉开差距的两节。前者证明你不是拍脑袋填数字,后者证明你真的读懂了数据。

数据部分只放关键图表,聚合报告截图、TPS 曲线、响应时间曲线各一张就够,剩下丢附录。我见过报告里贴了二十张图,正文一句话没有的,那种很难拿高分。

7.2 评委真正关心的三件事

第一,你的测试是不是可复现。脚本、数据文件、执行命令、参数配置都在,别人照着能跑出差不多的结果。

第二,你的结论有没有数据支撑。说"系统存在性能瓶颈"就要指出在多少并发、哪个事务、响应时间涨了多少。

第三,你有没有边界意识。比如说明白这次测试只覆盖了登录和查询两个事务,不涉及文件上传和批量导出,测试结果不能外推到整个系统。这种自我设限反而是专业的表现。

还有个小细节:报告里的时间、版本号、环境参数要前后一致。前面写的是 JDK 11,后面截图里显示 JDK 17,评委会觉得你不严谨。

我个人在几次练习和比赛里最大的体会是:性能测试的胜负手不在工具,而在"想清楚"。动手之前先花十分钟把负载模型算出来、把事务边界画出来,后面能省下几个小时反复调脚本的时间。另一个小技巧是,每次压测都把命令行参数完整记在一张便签或者 README 里,包括那次改了哪个配置、为什么改——过两天回头看数据,你会感谢当时的自己。这套流程跑顺之后,你会发现它不只是应付比赛,日常做接口压测、上线前评估容量,用的都是同一套路子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询