JMeter性能测试脚本录制与开发:从代理录制到分布式压测
2026/9/19 16:36:46 网站建设 项目流程

简介:这是一份面向软件测试初学者的实验报告,完整记录了基于LoadRunner对飞机订票系统开展性能测试脚本录制、增强与运行分析的实践过程。报告以Windows7和UTF应用软件为环境,涵盖HTML-based与URL-based两种脚本录制方式对比、Step Navigator脚本查看与重命名、事务插入、用户名密码参数化、集合点设置、Controller运行分析以及Design Studio自动关联等关键操作,同时配有大量界面截图、脚本代码和实验步骤说明,便于读者按图索骥、理解性能测试脚本的开发思路。内容还包含订票流程综合应用、座位参数化、lr_out_message输出函数及扩展日志设置等进阶技巧,能帮助学习者掌握处理动态数据、模拟并发用户和优化脚本效率的方法。压缩包内共1个doc文件,大小6.2MB,适合配合软件功能测试课程或LoadRunner自学使用,已有419人学习浏览。

1. 性能测试脚本不是录完就能跑:录制和开发的分界线在哪

实验5要交的报告叫“性能测试脚本录制和开发实验报告”,但真正考验人的不是写报告,而是能不能把“录制”和“开发”两件事分开。用工具自带的录制器抓出来的请求,本质上只是浏览器操作的回放,里面全是写死的用户名、时间戳和一次性 token,换一台机器、换一个账号就失效。所谓“开发”就是把这份原始回放整理成能参数化、有关联、有断言的性能测试脚本。录制解决的是“哪些请求以什么顺序发出”,开发解决的是“这些请求在并发场景下如何模拟真实用户”。两者之间有一条明确的分界线:脚本能在单机环境稳定跑完一遍之后,开发才刚过半。接下来以 JMeter 为工具,从代理录制、参数化、关联、断言到分布式压测,把这套流程说清楚。适合正在做性能测试课程设计的在校生,也适合刚接手压测任务、想少踩坑的测试工程师。

2. 用 JMeter 录制 HTTPS 脚本:代理配置与证书处理

2.1 录制前的测试计划结构

录制不能直接打开 JMeter 就开始点。先建一个测试计划,然后在计划下添加一个线程组,再在线程组上右键选择“添加 → 逻辑控制器 → 录制控制器”。这个录制控制器会把代理服务器捕获到的采样器自动归拢到一起,形成一棵树,后续你可以在控制器上统一添加断言或后置处理器。如果不建录制控制器,录制到的请求会平铺在线程组里,一旦请求数量上百,根本看不出登录、查询、退出之间的层级关系。

线程组的线程数在录制阶段可以保持默认的 1,因为录制只关心单用户路径是否完整。真正并发数在性能压测阶段才设置。录制控制器内部还要注意“暂停”属性,这个属性决定代理是否自动插入思考时间。如果只想采集请求,把暂停时间设为 0,后面开发阶段再按需插入思考时间。合理的测试计划结构应该是:测试计划 → 线程组 → 录制控制器 → HTTP 采样器。

在设计测试计划时,我习惯把录制控制器单独放在一个线程组里,命名为“录制原始脚本”,压测时再另建一个线程组,把参数化后的采样器复制过去。这样原始录制脚本可以作为参照物,一旦压测脚本调出了问题,可以对比原始请求头和数据。实验报告里也可以贴出这个结构图,能明显看出你懂脚本分层。

2.2 HTTP(S) Test Script Recorder 的代理参数设置

添加代理服务器的方式是:测试计划 → 添加 → 非测试元件 → HTTP(S) Test Script Recorder。窗口打开后,需要设置四个关键字段:端口、目标控制器、分组、记录 HTTP 信息头。端口默认是 8080,如果本机已经有服务占用 8080,改成 8081 或 8888,同时浏览器代理也要改为相同端口。目标控制器直接选择刚才建的录制控制器,这样代理才知道把写入请求放到哪个节点。

字段推荐值说明
端口8080 或 8888必须与本机空闲端口一致
目标控制器录制控制器右键下拉选择需要写入的节点
分组每个组放入新的控制器按页面或事务切分请求,后续好维护
记录HTTP信息头勾选保留 Content-Type、Cookie、Authorization 等头

分组选项里还有“每个分组放入新的控制器”和“无分组”。建议选“每个组放入新的控制器”,这样 JMeter 会根据页面跳转分组,但分组逻辑依赖响应时间和页面切换,有时候会把异步请求拆到错误的分组。如果录制过程中发现请求顺序乱了,也可以选“无分组”让所有请求平铺,后面自己整理。

“记录 HTTP 信息头”这个选项很重要。不勾选的话,即使请求带了Authorization: Bearer,录制时也会被丢弃,回放必然失败。如果录制目标是 HTTPS 接口,还要在 HTTPS 设置里确认“信任所有证书”已经打开,否则建立代理链路时可能因为证书链不完整而失败。

2.2.1 HTTPS 证书安装

HTTPS 录制本质是中间人解密。JMeter 在首次启动录制服务器时,会在bin目录下生成一个apachejmeter-temporary-rootca.crt根证书。浏览器如果不信任这个证书,就会弹出安全警告,甚至拒绝连接。安装证书的路径因系统而异,Chrome 在chrome://settings/security,Firefox 在“设置 → 隐私与安全 → 证书 → 查看证书”中导入。导入时一定要选择“受信任的根证书颁发机构”存储区,而不是“个人”存储区。

证书安装后,浏览器代理大概率就能记录到 HTTPS 流了。如果仍然出现SSL peer handshake failed,先检查系统时间。证书校验对时间敏感,系统时间偏差超过几分钟都可能握手失败。另外,公司电脑常装有企业证书,可能和 JMeter 临时证书冲突,这时可以尝试把 JMeter 的监听端口改到 443 之外的任意端口,然后重新启动录制服务器并再次导入证书。证书问题不是思路问题,是链路细节,实验报告里最好把证书导入前后的报错截图都保存一份。

2.3 浏览器代理配置与录制步骤

设置浏览器代理有两种常见做法。一是直接在系统设置里把 HTTP 和 HTTPS 代理指向127.0.0.1:8080,这样所有浏览器流量都会经过 JMeter。好处是简单,坏处是系统内其他软件的请求也会被录制,脚本里会出现很多非业务请求。二是用 Chrome 启动参数创建独立的无痕窗口,只让当前窗口走代理,命令行如下:

chrome --user-data-dir=/tmp/jmeter-profile --proxy-server=127.0.0.1:8080

用这条命令启动的 Chrome 窗口和日常浏览数据隔离,代理配置也只对这个窗口生效,录完即关,不会污染本机网络环境。需要说明的是,--user-data-dir是本次 Chrome 数据存储目录,如果不指定,Chrome 可能复用已有进程导致参数不生效。用这个技巧可以在录制和日常上网之间快速切换。

启动代理服务器、配置好浏览器后,按业务主链路操作一遍:登录、查询列表、打开详情、提交数据、退出登录。操作之间留出可识别的停顿,不要点太快,否则 JMeter 无法准确区分事务边界。录制完成后,回到 JMeter,点击录制器上的“停止”按钮,然后马上关闭浏览器窗口,避免后续请求继续写入。此时录制控制器下会生成一批采样器,建议先不要做任何修改,直接在“查看结果树”里执行一次单线程回放,看看有没有红色错误。

2.4 录制结束后的脚本去噪

录制出来的请求包里除了业务接口,还有大量静态资源和埋点。静态资源如*.js*.css*.png在性能测试里通常不构成业务瓶颈,应该过滤。过滤有两个层面,一是在录制服务器上通过“排除模式”直接不录制,二是在录制后手动删除。我的习惯是双保险:在录制前把排除模式写全,录制后按 URL 再扫一遍。

常见的排除正则如下:

.*\.(js|css|bmp|png|jpe?g|gif|ico|woff2?)(\?.*)?

这个正则可以搭配录制器“请求过滤 → 排除模式”使用。注意(\?.*)?是为了匹配文件名后带查询参数的静态资源,比如/a.css?v=123。如果不加这个后缀,这类请求不会被过滤。录制完成后的去噪顺序是:先按采样器名称排序,把所有名字带.js.css.png的删除;再找 URL 里含collecttrackmetricsreport的上报接口,这些接口由埋在页面里的第三方 SDK 触发,和核心业务无关。实验报告里不能把这些请求留在脚本中,否则会严重拉高吞吐量,导致报告失真。去噪结束后,把每个保留的采样器改成可读名称,如“登录接口-提交凭证”、“订单列表-查询”,后续聚合报告里显示的就是这些名称,而不是一长串 URL。

3. 脚本开发:参数化、关联和断言让录制脚本变成可回归的资产

录制脚本经过去噪后只是半成品,接下来要处理的变量替换是开发的核心。开发的目标是让脚本在每一次运行时都能通过不同数据完成同一业务动作。先想清楚哪些值会变:用户信息通常变,订单时间一定变,csrf token 和 session id 必须从服务器动态获取。把这些值抽象出来就是参数化,从请求响应中拿值再交给下个请求就是关联。

3.1 参数化的三种常用方式

3.1.1 CSV 数据文件

模拟多用户压测时不建议用固定账号,否则所有压力都打在一个账号上,缓存一旦生效,测试结果会高得离谱。准备 CSV 文件是最常见的做法。先建一个users.csv,内容格式如下:

username,password u_demo_01,Passw0rd#2024 u_demo_02,Passw0rd#2024

在测试计划中添加“CSV 数据文件设置”,配置文件名指向users.csv,变量名填username,password,分隔符用逗号。这里要理解 JMeter 的共享模式:默认是“所有线程”,意思是所有线程共享一个指针,每取一行后指针后移,一个迭代占用一行;如果选择“当前线程组”,每个线程组各自持有一个指针;选择“当前线程”,则每个线程独立读取,适合需要每个线程都从第一行开始反复使用的场景。压测场景一般选“所有线程”,因为这样最接近随机分配用户数据。

CSV 文件的编码有一个大坑:Windows 下使用记事本保存文件会默认带上 UTF-8 BOM,JMeter 读取第一行时会把 BOM 拼进第一个变量名,导致第一个请求的变量引用失败。常见表现是只有第一行数据出错,后续数据正常。解决办法是把文件另存为 UTF-8 无 BOM 格式,或者用 VSCode 重新保存。

提示:如果 CSV 文件里的密码包含${,,记得用引号包围字段;在 JMeter 的 CSV 配置里同样有“允许带引号?”选项,默认是 False,遇到特殊符号要打开。

3.1.2 用户定义的变量

如果某些参数在整个测试周期内都不变,例如服务器 IP、端口、公共请求头里的Client-Id,可以放在“配置元件 → 用户定义的变量”里。这个元件的变量在测试计划启动时初始化一次,所有线程共享同一份副本。它和 CSV 的根本区别在于:CSV 的数据是按迭代动态切换的,而“用户定义的变量”是静态的。如果你把一个用户名放进去,那么所有线程都会用这个名字,压测就会退化成单用户重复提交。

用户定义的变量适合做环境切换。把protocolhostport定义好,所有 HTTP 请求的路径写成http://${host}:${port}/api/xxx,换环境时只需要改一处,脚本不用动。这也是实验报告里体现工程化的一部分。不过要注意,不要在变量名中使用 JMeter 的内置属性占位符,否则会出现递归查找问题。

3.1.3 函数助手 __Random

对于流水号、手机号、订单号这类不参与业务校验的字段,用__Random函数最省事。在需要随机值的位置直接写:

${__Random(10000,99999,orderId)}

上面表达式的意思是生成 10000 到 99999 之间的随机数,存到orderId变量中,后续可以用${orderId}引用。这个函数在每次采样器执行时都会重新生成,这可能带来一个问题:同一个用户在一次登录流程中,两个请求都要提交orderId,而它们取到的随机值不一样。如果需要一次迭代内保持相同,就要把函数放到“用户参数”里,或者用setProperty做全局属性。最简单的变通是直接采用字符串拼接方式,比如${__time(/1000,)}-${__Random(1000,9999)},生成一个包含时间戳和随机数的订单号。

3.2 关联:解决动态 Token 与 Session

录制完成后,你会发现某些响应里的 token 是服务器动态生成的,比如登录后的Set-Cookie。如果脚本里把这些值写死,回放第二次就会因为会话过期而失败。关联可以简单理解为“从前面的响应中提取变量,再传给后面的请求”。零基础的同学第一次做往往不知道从哪找这个动态值,可以在“查看结果树”里看第一个请求的响应数据,复制 token 值,然后在脚本里搜索这个值出现在哪些后续请求中,从而确认关联点。

3.2.1 用正则表达式提取器

当响应是 HTML 时,正则提取器是最容易落地的。比如登录页返回<input type="hidden" name="_token" value="a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6">,后续的下单请求必须带上这个_token。在登录请求上添加“后置处理器 → 正则表达式提取器”,配置如下:

引用名称:token 正则表达式:name="_token" value="([a-zA-Z0-9]{32})" 模板:$1$ 匹配数字:1

逐项解释:引用名称是后续引用时的变量名,格式${token}。正则表达式的括号表示捕获组,[a-zA-Z0-9]{32}匹配 32 位英文字母和数字,$1$表示取第一个捕获组。匹配数字 1 的意思是取第 1 个匹配项,因为 token 在页面中只出现一次。在提交请求的参数里,把原来的_token=a1b2c3d4...直接替换为_token=${token}。回放时,登录请求的响应会先被提取器处理,提取到值后,下单请求再执行,token 总是最新的。

正则写起来要注意贪婪匹配。默认情况下.*会匹配到最后一个双引号,容易把值截多。稳妥的做法是在待匹配值两侧写清楚边界:左边是value=",右边是"。如果你的响应里有两个 token,就多写一个捕获组,并调整“模板”为$2$。花一点时间在“查看结果树”里验证正则,比瞎猜效果好得多。

3.2.2 用 JSON Extractor

现在大多数接口返回 JSON,用 JSONPath 提取比正则直观。比如登录接口返回:

{ "code": 0, "data": { "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiMTIzIn0.test" } }

在对应请求下添加“JSON 提取器”,配置是:引用名称填access_token,JSONPath 表达式填$.data.access_token,匹配数字填 1。JSONPath 的$.开头表示整个响应体,data是下一级字段。这段表达式按层找字段,比正则更抗格式变化。如果响应体是一个数组,比如{"data":[{"token":"aaa"}]},则表达式需要写成$.data[0].token。注意数组下标从 0 开始。

拿到 token 后,后续请求的“HTTP 信息头管理器”里添加一项:Authorization: Bearer ${access_token}。如果录制时信息头已经存在,直接替换 value 即可。这里常见错误是 JSON 提取器配置了但变量始终为空,原因大多是上一个请求已经失败,自然没有响应可提。排查时先确保前置请求成功,再检查 JSONPath 是否正确。

3.3 断言与监听器配置

写性能测试脚本必须加断言,否则返回 200 的业务错误页会被当成成功。最常用的是“响应断言”,配置在单个 HTTP 请求下面,比如预期{"code":0}"success": true。断言添加位置:右键请求 → 添加 → 断言 → 响应断言。

断言项配置位置常用值
响应文本模式匹配规则包含字符串
响应代码要测试的响应字段200
响应信息忽略状态不勾选

断言里的“模式匹配规则”默认是“包含”,也就是只要响应文本里出现你填写的字符串就算通过。不要用“相等”,因为响应体往往包含换行和时间戳,完全相等很难满足。在一个流程中,登录、提交、结果查询三个关键动作各加一个断言,这样就能从错误率中快速定位是哪个环节失败。

监听器方面,调试阶段使用“查看结果树”,压测阶段尽量去掉一切监听器。必要的话保留一个“聚合报告”,但也要等到命令行跑完之后再打开。图形化监听器在 GUI 模式下会实时生成图表,占用的内存可能超过脚本本身,导致压测结果失真。如果实验报告需要截图,可以在命令行结束后把 jtl 文件导入 GUI 再查看,而不是边压测边开着监听器。

3.4 调试技巧:用 Debug Sampler 和 View Results Tree

脚本写了参数化和关联后,最头疼的是变量没有按预期取到值。添加“调试取样器”可以快速看到当前线程的所有变量。添加方式:在采样器后面右键 → 添加 → 采样器 → 调试取样器。运行脚本后,在“查看结果树”里点击“调试取样器”,展开树形节点就能看到变量名和值。这个技巧比在代码里加日志直观得多,适合零基础用户。

调试取样器的输出会包含所有 JMeter 变量,数据量很大。正式压测前必须删除,否则每个线程每次迭代都会打印一大片变量内容到结果文件,jtl 体积膨胀数十倍。另一个技巧是使用“JSR223 后置处理器”加一行log.info(vars.get("token")),可以在日志中只输出一个变量,避免刷屏。两种方式各有利弊,初学者先用调试取样器,跑通后再换成日志方式。

4. 从单机脚本到分布式压测:参数调整与常见瓶颈

单机脚本跑通后,实验的下一步是让脚本产生真实并发。这时要调的不是 URL,而是线程组参数。很多新手把线程数调到 500,结果自己本机卡死,服务器却没多大压力。先理解线程数代表的是并发活跃线程,不是连接数,也不是 TPS。

4.1 线程组参数设置

三个必调参数是线程数、Ramp-up、循环次数。最简单的估算公式是:线程数 = 目标 TPS × 平均响应时间(秒)。注意平均响应时间不是单个请求的,而是整个事务的 P50 或 P90。比如测试目标是交易 TPS 100,事务平均耗时 0.5 秒,那么建议并发数就是 50。用 Python 计算如下:

target_tps = 100 avg_response_time = 0.5 # 秒 concurrent_users = int(target_tps * avg_response_time) print(f"建议线程数: {concurrent_users}")

Ramp-up 代表线程启动间隔。如果 50 个线程都设为 0,就是瞬间全部发起,可能会触发服务器的限流或 WAF 拦截。一般规则是让启动速率等于每秒新增 1-2 个线程,也就是 Ramp-up = 线程数 / 每秒启动速率。比如 50 个线程,Ramp-up=25 秒,等价于每秒启动 2 个线程。这样做能看到从低到高的性能曲线,而不是一个压力尖峰。

参数作用推荐设置
线程数并发用户数target_tps × 事务平均响应时间
Ramp-up线程启动时间线程数 / 期望启动速率
循环次数每线程迭代数压测时长足够时选“永远”

循环次数和调度器“持续时间”是二选一。如果设置了持续时间 600 秒,循环次数不会成为结束条件。实验报告里应该记录持续时间,特别是需要分析稳定性场景的报告。另外,线程组下面不要随意勾选“每次循环在独立线程组”之类的选项,它会强制每个循环新建线程,实际并发行为很难理解。

4.2 脚本分层的两个场景:前端性能测试与后端接口压测

录制脚本包含静态资源请求,但不同压测场景对这些请求的处理方式完全不同。做后端接口压测时,脚本里只保留业务接口,禁用“HTTP 缓存管理器”,因为缓存会让服务器少算很多业务逻辑,导致测出的 TPS 不是真实接口能力。而做前端整链路压测时,要保留一部分静态资源,并打开缓存管理器模拟老用户回访,否则每个线程第一次访问都会拉取所有静态资源,会明显高估服务器压力。

这两种场景没有绝对的对错,关键是在实验报告开头写明“本实验面向服务端接口能力,因此过滤掉静态资源”。如果你不说,别人看着脚本里有图片、有 CSS,会质疑你的测试目的。另外,事务控制器可以把多个采样器组合成一个逻辑事务,比如“登录 + 查询 + 创建订单”作为一个事务,聚合报告里只能看到这个事务的平均响应时间,不会看到内部细节。这个做法在两种场景里都适用。

4.3 回放时的高频报错与排查

开发完脚本后,回放最容易遇到三类错误。第一类是 401/403,这是关联没生效,或者是参数化的用户名没有对应权限。排查方式:在“查看结果树”里点开失败的请求,切到“请求体”标签,看Authorization头和 Cookie 里的值是否和上一个请求响应中的提取值一致;如果变量没有被替换,${token}会原样出现在请求头里。第二类是 500,多为业务参数错误,比如订单号被随机函数生成了超出范围的值。第三类是连接超时,常见于线程数开得很大,本机文件描述符不够用。

还有一个隐藏陷阱:JMeter 默认每次请求都会新建 TCP 连接。在 HTTP 采样器的“高级”标签里,把“使用 KeepAlive”保持勾选,添加“HTTP 缓存管理器”,可以复用连接,降低压测机自身的连接开销。如果错误消息是Address already in use: connect,说明本机端口耗尽,需要调大系统临时端口范围,而不是继续增加线程数。

4.4 分布式压测的脚本一致性与远程启动

单台压测机的线程数有限制,超过 500 线程往往需要多台机器联合压测。JMeter 支持 master 和 slave 架构,master 负责分发脚本和汇总结果,slave 负责实际执行压力。分布式压测前,必须保证脚本里的所有 CSV 数据文件、JAR 包、资源文件在每台 slave 机器上存在且路径一致。JMeter 不会自动同步这些依赖项,这是分布式脚本开发最常见的坑。

启动 slave 是在每一台机器上运行jmeter-server脚本,然后 master 执行命令:

jmeter -n -t perf_test.jmx -R 192.168.1.10,192.168.1.11 -l result.jtl

-R参数后面的 IP 列表是 slave 地址,master 会把这些 slave 上的线程组加起来作为总压力。如果脚本中有用户变量和 CSV,所有 slave 上的文件路径必须一模一样,建议把数据文件放在 JMeter 安装目录的bin下,并统一使用相对路径。分布式模式下的结果文件是汇总后的,但每个 slave 的启动时间可能有数秒偏差,分析报告时如果发现响应时间前段轻微分层,可以先检查各 slave 的时钟同步,用 NTP 同步时间。

5. 脚本回放验证的四个步骤和一条命令行回归命令

在提交实验报告前,脚本必须经过一次可重复的回放验证。我的验证顺序是:第一步,单用户回放整个流程,确认每个采样器都通过断言。第二步,连续跑三轮小并发(比如 5 个线程循环 3 次),观察变量是否能在每次迭代中正确变化,特别是 token 和 session 是否每次都不同。第三步,对比两轮相同压测命令产出的 jtl 文件,确认 TPS 和错误率波动在合理范围内,如果波动超过 10%,就要检查脚本里是否有随机函数引起的业务边界问题。第四步,用命令行生成 HTML 报告留档。

命令行回归命令是:

jmeter -n -t perf_test.jmx -l result.jtl -e -o /tmp/html-report

简单拆解一下:-n表示非 GUI 模式,-t指定测试计划文件,-l写结果文件,-e生成 HTML 报告,-o是输出目录。输出目录必须是尚不存在的路径,否则 JMeter 会报Output directory is not empty。在实验报告里记录这条命令,再加上执行前后的结果文件对比,就能证明脚本具备可回归性。

验证时还要注意一个反模式:把“查看结果树”留在脚本里,然后在 GUI 模式下跑压测。高线程数下,结果树会把每个响应都缓存到内存,用不了多久 JVM 就被塞满,整个脚本假死。正确的做法是:脚本里只放业务采样器和必要的断言,所有监听器都删掉,或者只在命令行结束时用-j参数捕获日志。想分析数据时,把 jtl 文件导入 GUI 内的“聚合报告”查看。

最后一步,打开生成的 HTML 报告,重点看Percentiles表和Response Time Overview图。如果 P95 明显高于 P50,例如 P50 是 300ms,P95 是 1200ms,说明存在明显的长尾请求。这时候不要急着加线程数,先回到 jtl 里找出最慢的采样器名称,再检查脚本里是否有同步导致的等待。响应时间的分布往往比平均值更有说服力,把这一段对比写进实验报告,能体现你理解性能测试而不只是会点“开始”按钮。

本文还有配套的精品资源,点击获取

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

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

立即咨询