第一次打开JMeter的新手,十个里有九个会先找“录制”按钮——不是大家懒,而是被测系统的请求结构有时候确实复杂,手写HTTP请求容易漏掉Header、漏掉隐含参数。“录制测试脚本”这件事,在JMeter里有一套完整的方法论:它不是简单地抓包回放,而是要让你在短时间内生成一个能跑、能压、能出报告的脚本骨架。这篇文章就围绕JMeter录制脚本的完整链路展开,从方案选型、环境配置、HTTPS证书处理,到录制后的整理增强,再到高频报错排查,把整个流程里值得注意的细节都过一遍。适合刚上手JMeter、或者已经能用JMeter做简单接口测试但一直没把录制用明白的测试工程师参考。
1. 录制方案选型:先搞清楚你需要的到底是不是“录”
1.1 录制测试脚本的本质是什么
JMeter的录制功能,通俗理解就是在本机架一个“监听员”:你打开浏览器正常操作被测系统,JMeter把这些操作发出的HTTP请求全部记录下来,自动转成一个个Sampler,最后组合成一份.jmx脚本。这个机制的学名叫HTTP(S) Test Script Recorder,本质上是一个本地监听端口,它不关心你是点了按钮还是拖了页面,只关心浏览器发出了什么请求、带了什么参数、收到了什么响应。
很多人把录制想简单了,以为“录完就能压”。实际上录制只是拿到一份原始素材,素材里包含大量无用请求,比如图片、CSS、JS、埋点统计,这些基本都要过滤掉;同时真实用户的操作往往带有动态数据,比如登录后拿到的token、表单里的防伪标记,这些如果不做参数化,脚本就只能跑一次。所以我把录制定位成“快速生成脚本骨架”的手段,真正决定脚本质量的,是录制之后那一步整理增强。
1.2 三套录制方案的取舍
先说Badboy。这是一个老牌网页录制工具,录制完可以导出成JMeter能识别的.jmx文件,优点是录制时有可视化界面,能看着脚本结构一步步点,缺点是已经停止维护很久了,和JMeter新版的兼容性越来越差,遇到前后端分离的动态请求,录制效果并不理想。现在新入行的测试同学没必要再学它。
再说JMeter内置的HTTP(S) Test Script Recorder。这是我最推荐的方式,JMeter自带,不需要额外装软件,录制出来的请求天然就是JMeter的Sampler结构,和线程组、断言、参数化功能无缝衔接。缺点是需要手动在浏览器设置监听端口,HTTPS还要处理证书,稍微有点门槛,但门槛不高。
还有第三种,用Fiddler或Charles这类抓包工具录制后转成脚本。这种方式录得更细,能看到每个请求的完整时序,但转换成JMeter脚本时依赖导出插件,请求头、Cookie经常丢,后期修正成本比前两种都高,适合分析问题,不适合快速生成压测脚本。
我的建议很明确:能用内置录制器就别折腾第三方工具。尤其是JMeter 5.x之后的版本,证书处理、域名过滤、URL重写这些细节都做得比较完善,整套流程保持在JMeter生态里,后期维护成本最低。Badboy那一类工具在2010年前后很流行,现在用反而是给自己添堵。
2. 录制准备与核心配置:把监听端口这件事想明白
2.1 环境准备与基础安装
先说安装。去Apache JMeter官网下载,找“Download Releases”入口,下载zip包(Windows)或者tgz包(Linux/macOS)。JMeter是纯Java应用,依赖JDK 8以上,所以第一步先把JDK装好。下载后解压到纯英文路径,目录里会有bin、lib、docs等文件夹,lib/ext目录专门放扩展插件。Windows下双击bin/jmeter.bat启动,Linux/macOS用bin/jmeter.sh。启动后如果觉得界面字体小,不要急着找设置按钮——JMeter的界面字体在bin目录下的jmeter.properties里调,找到jsyntaxtextarea.font.size和jmeter.hidpi.mode等配置项,也可以直接在界面“选项→外观”里切换。我习惯把字号调到15左右,长时间看聚合报告不费眼。
安装本身不算复杂,但我见过不少人卡在“启动闪退”上:要么JDK版本太低,要么JAVA_HOME没配好,要么解压路径带了中文。JMeter 5.x要求JDK 8及以上,JDK 9到11也能跑,但有些插件兼容性不好,所以如果只是做常规HTTP接口测试,装JDK 8稳定版最省心。
2.2 配置HTTP(S) Test Script Recorder的完整步骤
打开JMeter,先创建一个测试计划,然后按下面顺序操作。
- 添加线程组:右键测试计划,添加→线程(用户)→线程组。线程数可以先设为1,录制阶段不需要并发。
- 添加录制控制器:右键线程组,添加→逻辑控制器→录制控制器。它的作用是给录制到的请求一个存放位置,同时方便你只录制不跑。
- 添加HTTP(S) Test Script Recorder:右键测试计划,添加→非测试元件→HTTP(S) Test Script Recorder。
- 配置监听端口:在录制器面板里,端口默认8888,这个端口就是浏览器要走的监听端口。如果本机已有程序占用8888,改成8889或其他空闲端口。
- 设置目标控制器:选择刚才添加的“录制控制器”,这样录制到的请求才会落到线程组下面的控制器里,而不是散落在测试计划根部。
- 配置“HTTPS Domains”:这里填被测系统的域名,作用是让JMeter自动处理该域名下的HTTPS证书校验,减少录制时的证书报错。
这里有一个新手最容易漏的地方:录制控制器必须在线程组下面,否则录制时请求不会进线程组,后面压测时你会找不到那些Sampler。还有一个点,录制之前先在线程组下面加一个“察看结果树”监听器,这样录制过程中就能实时看到当前请求有没有录进来,不用录完再去找。
2.3 浏览器监听设置与HTTPS证书导入
监听端口配好后,还要让浏览器把流量交给JMeter的这个端口。这个动作在浏览器里叫“手动配置代理”,这里说的是本机测试环境的配置,只是把浏览器的请求导向本机的JMeter端口,不涉及任何外部网络。以Firefox为例:打开设置→网络设置→手动配置代理→HTTP代理填127.0.0.1,端口填8888,勾选“也适用于HTTPS”。
配置完成后,打开浏览器访问一个HTTP页面,回到JMeter的录制控制器里,能看到请求一条条冒出来,说明监听成功。但如果被测系统是HTTPS,你还会遇到证书报错——浏览器会提示“您的连接并不安全”,因为JMeter动态生成的证书不是浏览器默认信任的。解决思路是导出并信任JMeter证书。
在录制器面板点击“启动”后,JMeter会自动生成一个ApacheJMeterTemporaryRootCA证书。浏览器访问http://127.0.0.1:8888就能下载到这个证书文件,实际会生成一个crt文件。把这个证书导入到浏览器的“证书颁发机构”列表中,勾选“信任此CA来标识网站”,然后重启浏览器,HTTPS录制就能正常走了。Firefox和Chrome的导入入口不同,但思路一样:先下载根证书,再在浏览器设置里导入并信任。Chrome走的是操作系统级证书库,Windows下要双击证书文件导入到“受信任的根证书颁发机构”。
我踩过的坑:证书导入后,如果浏览器还报证书错误,先检查系统时间是不是不对,证书有效期校验对时间非常敏感。另外导入证书前要把旧的临时证书清掉,否则浏览器会用旧证书导致校验失败。录完之后别忘了把浏览器的代理设置关掉,否则不录制时浏览器会一直把请求丢给JMeter,页面全打不开。
2.4 过滤规则:决定脚本质量的隐藏细节
录制面板里有一个“包含/排除”正则列表,这是很多教程不重视但实际非常影响脚本质量的地方。默认情况下,你录一个登录页面,可能录进来几十个请求:图片、字体、CSS、JS、统计SDK、favicon……这些静态资源在真实用户访问时确实会产生流量,但对性能测试来说,如果把几十上百个静态资源的请求全部保留,反而会稀释核心接口的响应时间数据,让报告看起来“平均响应时间很高”但不知道瓶颈在哪。
所以我在录制前一般会在“排除”里加上这几类:
.*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf)(\?.*)?:过滤静态资源.*\.(map|min\.js)$:过滤前端源码映射文件.*\/collect.*、.*\/metrics.*等业务无关请求:根据实际项目放行或排除
注意过滤规则用的是正则,而不是通配符。如果你不熟悉正则,最简单的办法就是先录一遍,然后回来看结果树,把不需要的请求的URL复制出来,在排除列表里加一行对应正则。这样做的好处是脚本体积小、逻辑清晰,后面加断言和参数化的时候不会乱。
3. 录制之后的脚本整理与增强
3.1 参数化:把写死的请求数据变成可复用变量
录制完成后,你会发现所有请求的URL、参数、Header全部是写死的。如果脚本只跑一遍,写死没问题;但压测要模拟100个用户并发,每个用户都带着同样的手机号、同样的订单号去请求,服务端会怎么反应?要么报重复数据,要么触发防重机制,要么缓存命中导致响应时间失真。所以参数化是录制脚本之后最必要的一步。
最常用的参数化工具是CSV Data Set Config。创建一个csv文件,第一行放变量名,比如username,password,phone,后面每行放一条测试数据。在需要参数化的请求里,把原来的值替换成${username}、${password}这种变量引用。特别要注意CSV组件的几个配置项:
- 线程共享模式:如果选择“当前线程组”,每个线程会按顺序读取csv的一行,线程之间不会重复;如果选择“所有线程”,多个线程会共同消费这个文件。很多人问的“同一个csv参数化文件中每个线程分块取值”,对应的就是“当前线程组”模式。它会把csv按线程数分块,每个线程取自己那块的数据,从而保证账号不串线。
- 遇到文件末尾Recycle:循环次数如果大于数据行数,建议勾选Recycle=false,Stop thread=TRUE,避免测试数据被循环复用导致冲突。
除了CSV,还有一种很常见的参数化方式是从数据库取值,也就是JDBC Request配合CSV或者直接配合参数的用法。做法是:先配置一个JDBC Connection Configuration,填写数据库URL、驱动、账号密码,然后通过JDBC Request查出一批数据,存到变量里,供后续请求使用。比如压测下单接口,先查出一批user_id,再把user_id传给下一个接口。如果查询结果只有一行,直接用变量名;如果有多行,可以配合计数器或者ForEach控制器逐条消费。数据库密码不要直接明文写在jmx里,建议用JMeter的配置元件里引用环境变量,避免脚本泄露。
3.2 断言与提取:让脚本真正会“判断”
录制脚本跑起来容易,但要让它自动判断“这次请求到底成没成功”,就必须要加断言。最简单的断言方式是响应文本包含:在请求下面加一个“响应断言”,模式匹配填“操作成功”这类业务关键字,如果响应里没有就会报错。这个方法很多人会,但实际项目里用得更多的是提取器。
因为现在的系统几乎都是前后端分离,登录后token、防伪标记这类动态参数,会在响应里返回。你就需要先用JSON Extractor或者正则表达式提取器把这些值提取到变量里,然后在后续请求中引用。JSON Extractor在响应是JSON格式时非常好用:写一个JSONPath表达式,比如$.data.token,匹配到之后存成变量token,后面的请求在Headers里写Authorization: Bearer ${token}即可。正则表达式提取器更通用,适合响应是HTML或者混合格式的场景,写法是"token":"([^"]+)",模板取$1$。
Beanshell断言是另一个高频需求点。它适合做复杂逻辑判断:比如响应里返回一个错误码列表,我想根据错误码做不同处理。在断言里写一段简短的Beanshell脚本,可以用vars.get()去取变量,用prev.getResponseDataAsString()去取响应内容,然后通过Failure = true;或FailureMessage = "...";控制断言结果。贴一个我常用的模板:
String resp = prev.getResponseDataAsString(); if (resp.contains("code\":200")) { Failure = false; } else { Failure = true; FailureMessage = "业务返回码错误, 响应内容: " + resp; }注意Beanshell在JMeter里属于性能较差的组件,压测时如果QPS要求高,尽量少用或者改用JSR223+Groovy。小流量验证功能时用Beanshell没问题,大并发场景我建议用Groovy脚本写断言,效率差好几倍。
3.3 特殊场景:上传文件与防伪标记
录制脚本时经常遇到上传文件接口。录制时你正常选文件、上传,录制下来的脚本里会看到multipart/form-data类型的请求,里面有个文件路径参数。这里最需要改的是把文件绝对路径改成相对路径或者参数化:我见过太多人把本地路径E:\test\照片.jpg写死到脚本里,结果脚本发到别人电脑上,路径不存在直接报错。正确做法是把要上传的文件放到JMeter的bin目录或者测试脚本同级目录,然后参数里写相对路径;如果压测机器是多台,还要考虑把文件分发到每一台压测机上,路径保持一致。
另一个高频坑是防伪标记。很多MVC项目会校验__RequestVerificationToken防伪令牌,录制脚本时往往第一次手动操作会带上这个token,但脚本回放时却报“未提供必要的防伪标记”。根因是token是动态的,和会话Cookie绑定,每次请求前都要先从页面或者接口拿到新的token。解决办法就是上面说的提取器:登录页或者首页响应里会把这个token藏在表单隐藏域里,用正则提取器或者XPath提取器把它取出来,在后续POST请求中用${token}替换。如果token在Cookie里联动变化,还要检查Cookie管理器是否勾选了“每次迭代清除Cookie”,否则上一次会话的Cookie会干扰这一次。
至于RESTful参数怎么写,录制的脚本里一般都能直接看到:GET请求参数拼在URL后面,POST请求参数在Body Data里,JSON格式的直接在请求体的JSON字符串中改写。但录制下来的请求往往带着一堆没用的Header,比如Accept-Language、Referer等,回放时Header太多反而容易触发服务端风控,建议只保留Content-Type、Authorization和必要的自定义Header。
3.4 并发验证与结果导出:录制脚本的最终检查
脚本整理完之后,先在线程组里设置线程数。第一次验证阶段,线程数设为1,循环1次,配合察看结果树确认每一个请求都是绿色通过、响应时间正常。确认没问题后,再按压测需求设置并发,比如线程数100,Ramp-Up Period设为10秒,循环次数根据场景来。这里的Ramp-Up Period意思是100个线程在10秒内逐步启动,而不是一开跑就100个线程同时打过去,这个设计是为了模拟真实用户逐步进入系统的过程,也能避免瞬间压垮服务端导致误判。
跑完之后重点看三类数据:聚合报告里的平均响应时间、90%响应时间、吞吐量;错误率;以及TPS曲线。具体到“模拟100用户并发报告”,你在聚合报告里能看到每个请求的样本数、平均响应时间、中位数、异常%等指标。如果异常%不为0,双击请求对应的行,结合断言失败信息定位是参数化数据冲突还是服务器报错。察看结果树里的请求,可以通过右键导出全部或者部分sample result,保存为JTL或XML格式,方便后续做二次分析和留存记录。导出的时候记得勾选“保存响应数据”,否则只有请求摘要,没有响应报文,出了问题很难追溯。
4. 高频报错速查与排查实录
4.1 录不上、证书信任与连接异常
第一个高频错误是录不上:浏览器配置了监听,但是JMeter里没有请求进来。排查顺序是这样:先看监听端口有没有被占用,命令行执行netstat -ano | findstr 8888;再看浏览器代理地址和端口是否和JMeter一致;最后看浏览器有没有开第三方插件拦截代理。还有种情况是录制时指定了HTTPS Domains,确保域名和实际访问地址完全一致,带不带www也要一致,有时候多了个前缀就录不上。
第二个高频错误是HTTPS录不了,页面显示证书无效。这类问题九成出在证书导入环节。检查顺序:证书有没有导入到正确的证书存储区;浏览器有没有重启;系统时间是否正确。另外还有一个冷门原因:JMeter生成的证书有效期是7天,如果你用的是很老的JMeter版本并且跨了星期,证书可能已经过期,重新启动录制器生成新证书即可。
第三个高频错误是运行时出现java.io.IOException: Error writing to server。这个报错一般出现在发请求的瞬间,本质是JMeter向服务器写数据时连接被重置,常见原因是服务端主动断开——比如你压测QPS过高,服务端连接池满了直接RST;也有可能是请求头带了非法字符或者Content-Length不对,导致服务端解析失败。排查思路是先降并发确认是不是过载问题,如果单线程也报,就抓一下请求内容,看有没有特殊字符。实际上很多时候是录制时把一些响应相关的头(比如Content-Length)也带上了,回放时造成冲突,删掉这类请求头就好。
4.2 文件冲突、乱码与数据重复
“文件已经存在”这个报错,通常出现在察看结果树导出结果或者自己配置了输出文件时:你指定的文件名已经存在,JMeter默认会拒绝覆盖。解决办法是在文件配置里勾选“Overwrite”或者“Append”,或者每次运行前先删掉旧文件。如果你是用命令行跑压测,还可以在命令里加时间戳动态生成文件名,比如result_20250101123000.jtl,既避免冲突又能保留每次压测的记录。
乱码问题也比较常见。录制脚本或察看结果树里中文变成??或者%E4%B8%AD,一般是编码不一致。第一种方案是确保被测系统响应是UTF-8,然后在察看结果树里切换编码显示;第二种是在取样器的Content Encoding里明确填UTF-8;第三种是修改jmeter.properties里的sampleresult.default.encoding=UTF-8。如果数据库里的中文乱码,那就是JDBC配置里没有加characterEncoding=utf8,加上即可。数据重复的问题前面说过,优先检查CSV参数化的线程共享模式和Recycle配置,别让两条线程拿了同一行数据。
4.3 插件扩展与压测执行节奏
热搜里看到有人问jmeter下载mqtt插件、插件包安装这类问题,一并说下。JMeter的插件生态很丰富,安装路径一般是:先下载JMeter Plugins Manager(一个jar包),放到lib/ext目录,重启JMeter后菜单栏会出现“选项→Plugins Manager”。在里面勾选需要的插件,比如MQTT协议支持、自定义线程组、PerfMon监控,JMeter会自动下载安装。但我的经验是插件能少装就少装:插件会改变JMeter的默认线程实现和部分行为,装多了之后升级、排错都会变麻烦。尤其是压测脚本,如果依赖了某个第三方插件,换一台没装插件的机器就跑不起来。尽量用JMeter原生能力解决问题,实在需要MQTT这种原生不支持的协议,再考虑插件。
最后分享一个关于执行节奏的心得。压测不是“脚本跑完看报告”就结束了,合理的流程是:先小并发冒烟,比如5个线程跑3分钟,确认脚本参数化没问题;再逐步升到目标并发的一半,观察TPS和错误率变化;最后才打到目标并发,稳定跑10到15分钟取数据。这样即使压出问题,也能区分到底是脚本问题还是环境问题,不至于一上来就稀里糊涂压出一堆Error,最后还不知道锅该甩给谁。
5. 关于录制脚本这件事,我最后想说的
常在测试圈看到两种极端:一种人觉得录制是“小白才用”的功能,高手都手写脚本;另一种人录完脚本拿过来就直接压,出了问题就甩锅给工具。这两种我都经历过。实际上录制功能最大的价值不是帮你省掉写请求的时间,而是帮你快速理解一个陌生系统的请求链路——登录页跳了哪些接口、下单时带了哪些参数、退出时清理了哪些会话,这些信息在手写脚本时很容易遗漏,但录制一遍就全暴露出来了。
我自己做压测项目的习惯是:录制只作为第一步,拿到脚本骨架后,一定会做三件事——过滤静态资源、参数化所有动态值、给关键接口补断言。这三个动作做完,脚本才算真正能用于压测。你花在脚本整理上的时间,通常会在压测执行阶段几倍地赚回来,因为不用一边压一边排查“这个error到底是脚本问题还是服务端问题”。
如果你刚开始接触JMeter,不妨从一个小项目开始练手:拿一个自己部署的Web应用,完整走一遍录制、整理、参数化、并发验证的流程。等这套流程跑顺了,再去看那些所谓的“高手脚本”,你会发现它们的核心骨架和你自己整理出来的脚本其实没什么两样。