ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JMeter BeanShell脚本入门:从环境搭建到接口测试实战

JMeter BeanShell脚本入门:从环境搭建到接口测试实战 做接口测试和压测的时候大家应该都遇到过一种尴尬Jmeter自带的元件很强大但碰到要从响应里提取第N个JSON字段再拼一段加密串传给下一个请求或者要写条复杂断言判断金额四舍五入后是否落在某个区间这类需求现成的组件不是做不到就是做得贼别扭。这时候BeanShell脚本就派上用场了。BeanShell是嵌入在Jmeter里的轻量级Java脚本解释器语法是Java的简化版写起来像脚本跑起来却能调用完整的Java类库。这篇是每周读书与学习系列里Jmeter BeanShell的第一篇先把背景讲清楚把环境搭好再带大家跑通第一个脚本适合刚接触Jmeter不久、想在测试里加一点自定义逻辑的同学也适合已经会用Jmeter但一直没系统搞懂BeanShell的老手。1. BeanShell在JMeter中的定位和用途1.1 什么时候需要用BeanShell很多新手刚接触Jmeter时会有个误解觉得既然有JSON提取器、正则提取器、断言组件脚本就没有用武之地了。这个想法在简单场景下没问题比如从一个JSON响应里提取某个字段JsonPath写一条表达式就能搞定。但事情一旦复杂起来现成组件就开始露怯。举几个我实际踩过的例子。第一个是参数拼接场景。接口A返回一个列表里面有几十个元素我要随机取其中一个再根据它的类型字段动态拼出下一个请求的URL。用JSON提取器配合循环也可以做但代码量长得吓人调试的时候眼睛都快瞎了。换成BeanShell三五行写一个循环逻辑清清楚楚。第二个是加解密场景。接口要求对请求体做AES加密密钥还要根据时间戳动态生成。传统做法是写一个Java类放在lib/ext下再从Jmeter里引用问题是每改一次逻辑就要重新编译打包来回折腾很痛苦。用BeanShell直接在脚本里写加密逻辑改完重跑即可非常方便。第三个是断言场景。响应里有一串JSON数组要求每个元素的金额字段都大于100且数量字段加起来必须等于请求里的总量。用响应断言写这种跨字段、跨元素的逻辑几乎不可能用BeanShell Assertion几十行脚本搞定。所以什么时候需要用BeanShell不是想用脚本而是现成组件表达不了业务逻辑或者写组件配置比写脚本还难的时候。BeanShell在Jmeter里的定位是最后一个兜底手段也是最能体现测试工程师编码功底的地方。1.2 BeanShell的核心价值BeanShell从名字就能看出来它天生就是给Java生态服务的。Jmeter本身是Java写的所有内部变量、上下文对象、测试结果本质上都是Java对象。BeanShell作为JVM里跑的解释器可以直接操作这些对象这是它最大的价值。具体拆开说BeanShell给测试脚本带来的好处有三个。第一是免编译。写一段Java逻辑不需要编译成class文件解释器直接执行源码。这对测试脚本场景特别友好因为测试逻辑经常要根据业务变化频繁调整编译-打包-重启这套流程在排障现场完全不可接受。第二是零配置集成。Jmeter内置了BeanShell相关组件不需要额外装插件虽然有个专门的BeanShell插件包但基础能力内置了。打开Jmeter就能写门槛极低。第三是能调用全部Java生态。BeanShell脚本里可以import任意Java类包括Jmeter自带的类、第三方库、甚至是你自己写的工具类。这意味着你在Java里能用的所有能力测试脚本里都能用。不过要提醒一句BeanShell能做的事JSR223 Groovy基本都能做而且性能更好。为什么还要学BeanShell因为Jmeter的老项目、老教程、公司沉淀的测试资产里BeanShell脚本存量非常大。你能读懂BeanShell就能接手这些测试资产也能更好地理解社区里各种脚本片段。所以入门阶段先学BeanShell打的是基础后面再迁移到Groovy也会轻松很多。2. 环境准备JDK与JMeter安装2.1 检查JDK与JAVA_HOME配置BeanShell脚本要在Jmeter里跑前提是Jmeter本身能正常启动而Jmeter是纯Java应用所以第一步永远是JDK。这里有个容易踩的坑Jmeter 5.x版本必须用Java 8以上的JDK如果你机器上装的是Java 7或者只有JRE没有JDKJmeter能打开但部分组件会报错。先说怎么检查。Windows环境下打开CMDLinux/Mac打开终端执行java -version。看到类似java version 1.8.0_301或更高版本号就没问题。如果提示找不到命令说明JDK没装或者环境变量没配好。装JDK我推荐用OpenJDK发行版比如Adoptium原AdoptOpenJDK提供的版本免费、无坑、持续更新。下载安装后还要确认JAVA_HOME环境变量。Windows下右键此电脑→属性→高级系统设置→环境变量新增一个系统变量JAVA_HOME值填JDK安装目录比如C:\Program Files\Eclipse Adoptium\jdk-11.0.21。然后在Path里加上%JAVA_HOME%\bin。配好后再开一个新的CMD窗口执行java -version验证。这里为什么强调新开窗口因为环境变量修改后已经打开的CMD不会自动刷新很多新手改了变量但CMD里还是旧值卡在这一步半天其实不是配置错了是窗口没重开。2.2 下载与启动JMeterJDK搞定后去Jmeter官网下载最新版本。选Binary包不要选Source包。下载完是个压缩包解压到一个没有中文和空格的路径下比如D:\tools\apache-jmeter-5.6.3这一点很重要。为什么强调路径不能有中文和空格因为Jmeter内部的脚本引擎、类加载器对中文路径的支持时好时坏尤其是BeanShell脚本里如果涉及到文件读取、类路径加载中文路径很容易出奇怪的错误。我自己就曾经把Jmeter放在C:\Users\张三\工具\下结果写BeanShell读取CSV文件时怎么也读不到换成纯英文路径后一次通过。所以宁可多解压一层也不要放在带中文的目录里。启动方式Windows下进入bin目录双击jmeter.batWindows或jmeter.shLinux/Mac。我这里推荐用命令行方式启动能实时看到日志输出遇到问题方便排查。Windows下在cmd里进入bin目录执行jmeter.bat能看到一堆日志滚动出来然后弹出JMeter的图形界面。启动后先做一个简单的验证左侧面板找到测试计划右键添加一个线程组再加一个查看结果树直接跑一个空请求确认环境基本可用。这一步不是多余的因为很多BeanShell脚本跑不出效果根因是Jmeter环境本身有问题而不是脚本写错了。先验证基础环境后面排查问题能少走很多弯路。3. BeanShell组件选型与脚本入口3.1 四大BeanShell组件怎么选Jmeter里带BeanShell字样的组件有好几个新手容易搞混。这里用一个表格把主要组件和最常见的使用场景理清楚。BeanShell组件所在菜单路径主要用途BeanShell Sampler线程组→添加→Sampler→BeanShell Sampler在请求之间执行一段脚本可以做数据处理、参数拼接、生成加密串等BeanShell PreProcessorSampler→添加→前置处理器→BeanShell PreProcessor在某个请求发送前执行脚本常用于动态参数准备BeanShell PostProcessorSampler→添加→后置处理器→BeanShell PostProcessor在某个请求响应后执行脚本常用于提取数据、保存变量BeanShell AssertionSampler→添加→断言→BeanShell Assertion在响应后执行脚本用脚本计算断言结果BeanShell Listener线程组→添加→监听器→BeanShell Listener在测试运行时收集数据一般用得少BeanShell TimerSampler→添加→定时器→BeanShell Timer动态计算延迟时间按需使用这么多组件新手不需要全部掌握。我的建议是先把Sampler、PostProcessor、Assertion这三个吃透覆盖了大部分实际场景。PreProcessor和Sampler的区别在于执行时机PreProcessor跑在取样器之前适合处理这个请求要用的数据Sampler自己就算一个取样器可以理解为脚本本身就是一步操作。举个例子你要构造一个签名参数给接口A用签名逻辑依赖接口A的URL和当前时间。这时候脚本放在BeanShell PreProcessor里最合适它在接口A的请求发出前执行把签名结果存入变量。而如果你的测试步骤就是要计算一个值并保存下来不需要依附任何请求那就直接加BeanShell Sampler。3.2 BeanShell内置变量清单BeanShell脚本之所以能无缝操作Jmeter的各种数据靠的是一组内置变量。这些变量不需要你手动声明脚本一执行就自动注入。我列出最常用的几个。vars变量工具类对应Jmeter的变量空间。最常用的是vars.get(varName)和vars.put(varName, value)。从JMeter变量、用户自定义变量、CSV数据文件里取数据都用它。log日志对象调用log.info(message)能把信息输出到Jmeter日志文件调试脚本时非常好用。prev当前取样器的结果对象类型是SampleResult。用prev.getResponseDataAsString()拿到响应内容prev.setSuccessful(false)主动把请求标记为失败。ctx上下文对象类型是JMeterContext。通过ctx.getVariables()可以拿到和vars一样的效果但ctx能访问更多上下文信息比如此前采样结果、线程信息。propsJMeter属性相当于全局配置。用props.get(propertyName)读取jmeter.properties里的配置。OUT控制台输出流OUT.println(xxx)会在Jmeter的控制台打印信息适合调试。还有一个不是内置但必须掌握的语法糖在BeanShell里可以直接用${varName}方式引用变量但这种用法有坑。它会先被Jmeter做变量替换再把结果拼到脚本里。如果变量值里包含引号、特殊字符脚本可能会崩。所以写真正的BeanShell逻辑时尽量用vars.get()只在场景非常确定的时候才用${varName}插值。4. 手写第一个BeanShell脚本4.1 Hello World与基础语法环境准备好组件也认识了现在动手写第一个脚本。这里我用BeanShell Sampler来演示因为它最直观。在测试计划里添加一个线程组线程组下添加BeanShell Sampler然后在脚本区域输入以下内容log.info( 第一个BeanShell脚本开始 ); // 定义一个字符串变量 String name test_user; // 用vars.put保存到JMeter变量空间 vars.put(username, name); // 从vars里读出来再拼接 String fullName vars.get(username) _ System.currentTimeMillis(); vars.put(fullName, fullName); // 打印到控制台 OUT.println(fullName fullName); log.info( 第一个BeanShell脚本结束 );跑一下这个Sampler打开查看结果树确认请求是绿色的再去Jmeter日志里看输出。这里有个需要注意的点BeanShell Sampler默认会有一个采样结果无论脚本里写没写请求逻辑它都会记录一个成功的采样。如果你把它当作辅助脚本来用不想让它出现在最终测试报告里可以在脚本末尾加一行prev.setIgnore()这样这个采样就不会被计入结果统计。我建议新手初期多研究log.info和OUT.println。这两条语句是调试利器肉眼看不到的内部变量值、分支走向、异常信息全靠它们输出到日志里定位。很多人问我脚本没生效怎么排查我说先把日志打开把关键点全打印出来99%的问题都能看出来。4.2 常用语法与函数速记BeanShell语法和Java几乎一样但有一些方便的小差异。这里整理一份高频语法清单都是实践中频繁用到的。变量声明和Java一样String s abc、int i 1、List list new ArrayList()。不需要加分号也能跑但我建议坚持写分号避免以后迁移到JSR223/Groovy时手滑。数组遍历和Java 8风格一致String[] arr {a, b, c}; for (String item : arr) { OUT.println(item); }条件判断、循环、三目运算符都和Java一致直接用。要说区别BeanShell允许动态类型也就是你写var x 1;也是可以的。但我个人的习惯是坚持写Java强类型语法这样脚本更规范复制到别的脚本引擎时改动也小。常用类方面System.currentTimeMillis()取时间戳、UUID.randomUUID().toString()生成随机串、java.time.LocalDateTime.now()取日期时间。日期格式化需要先用java.time.format.DateTimeFormatter和Java标准一样。文件读取可以用BufferedReader配合FileReader注意关闭流或者直接用Java 7的Files.readAllBytes一次性读入。还有一个很实用的小函数Integer.parseInt()和Double.parseDouble()。因为从vars.get()出来的全是字符串要做数值比较和运算必须先转换。新手经常忘了这一步直接用比较两个字符串结果永远不符合预期。5. 实际业务场景BeanShell断言与参数传递5.1 BeanShell Assertion写法详解用代码写断言比用图形化断言灵活得多这是BeanShell在接口测试中用得最频繁的场景之一。添加BeanShell Assertion的方式在某个Sampler下右键选择添加→断言→BeanShell Assertion。脚本里有一个内置对象FailureMessage和Failure没有主动声明但可以直接用。核心套路是当断言条件不满足时设置Failure true同时把FailureMessage赋值为报错信息。举个例子。接口返回的JSON数组里有一个status字段要求每一项都必须等于success。脚本可以这么写import org.json.JSONArray; import org.json.JSONObject; String response prev.getResponseDataAsString(); JSONArray array new JSONArray(response); for (int i 0; i array.length(); i) { JSONObject item array.getJSONObject(i); String status item.optString(status); if (!success.equals(status)) { Failure true; FailureMessage 第 (i 1) 个元素状态异常status status; break; } } if (!Failure) { log.info(断言通过共检查 array.length() 个元素); }这个脚本里有两个关键点。第一prev.getResponseDataAsString()拿到的是原始响应字符串如果Jmeter里配置了响应编码要注意编码问题这里先不展开。第二Failure和FailureMessage是断言组件内置的特殊变量直接赋值即可不需要声明。很多人在BeanShell断言里犯的一个错误是直接throw new RuntimeException(xxx)。这么写确实能让请求失败但断言结果里显示的堆栈信息很乱不如用FailureMessage来得清晰。除非有特殊需求否则默认写法就是设置Failure true并附上FailureMessage。5.2 跨请求传递动态参数做压测和接口测试时经常有下一个请求要用上一个请求返回的数据这样的需求。除了用JSON提取器BeanShell PostProcessor也是一种很顺手的方式。场景是这样的登录接口返回{token:abcdef}下一个查询接口的请求头需要带这个token。用JSON提取器当然可以但如果你还想在保存变量前对token做一次处理比如加上固定前缀、截取前20位JSON提取器就无能为力了这时候BeanShell PostProcessor就很合适。在登录接口下添加BeanShell PostProcessor脚本如下import org.json.JSONObject; String response prev.getResponseDataAsString(); JSONObject obj new JSONObject(response); String token obj.getString(token); // 做点处理统一加前缀方便后续排查来源 String finalToken Bearer_ token; vars.put(authToken, finalToken); log.info(成功提取token并处理finalToken finalToken);后面的查询接口只需要在HTTP请求的请求头管理器里写变量Bearer_${authToken}或${authToken}Jmeter会自动完成替换。这段脚本还有一个容易被忽略的细节vars.put存的变量默认只对当前线程生效。如果你在压测时开了多线程每个线程的登录响应都不一样那么vars.put写进去的结果是每个线程各存各的互不干扰。这个特性既是一个优点线程隔离也是一个陷阱不能跨线程取数据。如果你确实需要全局共享某个值比如所有线程都用同一个token一般会用props.put或属性组件处理而不是用vars.put。6. 常见问题与排查技巧实录6.1 中文乱码与编码问题BeanShell脚本里一出现中文就乱码是新手最常遇到的第一大坑。表象是日志里打出乱码或者是断言时中文比较不通过。根因分两类。第一类是脚本文件本身的编码问题。Jmeter的脚本编辑器默认编码取决于操作系统和Jmeter配置文件。修法是在Jmeter安装目录的bin/jmeter.properties里找到sampleresult.default.encoding改成UTF-8。同时用外部文件保存脚本时务必用UTF-8编码保存。第二类是HTTP响应的编码识别问题。prev.getResponseDataAsString()返回的字符串编码取决于响应头里的Content-Type。如果服务器返回的Content-Type没声明charset且响应内容里有中文Jmeter可能用ISO-8859-1去解码导致乱码。遇到这种情况在脚本里手动指定编码是最稳妥的String response new String(prev.getResponseData(), UTF-8);prev.getResponseData()拿到的是原始字节数组然后自己用指定的字符集解码这样就不会被响应头误导了。这是我在实际测试里最常用的一招压测时不少接口响应头不规范靠这个方法绕过了所有乱码问题。补充一点BeanShell脚本文件里如果有中文注释也建议确保Jmeter脚本以UTF-8保存。Jmeter本身对脚本内容的编码处理在有些版本里比较粗暴中文注释可能导致解析报错。如果遇到莫名其妙的语法错误可以把中文注释删掉试试排查是不是注释引发的。6.2 脚本运行快与慢性能问题必须说一个扎心的事实BeanShell的执行效率在Jmeter支持的脚本引擎里是偏低的。原因是BeanShell是解释执行每次运行都要重新解析脚本而JSR223 Groovy有缓存和编译优化。如果只是接口测试一个线程跑几个请求BeanShell的性能影响可以忽略。但如果是大并发压测脚本里又有大量JSON解析、字符串拼接耗时操作BeanShell有可能成为瓶颈甚至影响压测结果的真实性。我的建议是分场景处理功能验证、接口调试、小并发冒烟测试BeanShell随便用方便直观真正的高并发压测场景优先用JSR223 Sampler Groovy并在JSR223组件里勾选Cache compiled script if available。脚本逻辑本身可以先用BeanShell写好、调通再迁到Groovy里。两门语言语法接近迁移成本很低。这里也回应一个常见的疑惑既然Groovy更好为什么Jmeter还内置BeanShell历史原因Jmeter早期没有JSR223组件时BeanShell就是主推脚本方案存量资产太多。不是BeanShell不好是后浪太强。6.3 脚本环境与路径坑BeanShell脚本里如果是单纯调Java类库一般不会出路径问题。但一旦涉及读取文件、加载资源各种奇奇怪怪的坑就会冒出来。最常见的坑是相对路径。BeanShell脚本里写new FileReader(data.txt)这个相对路径是相对于Jmeter启动时的工作目录而不是脚本文件所在目录。我实测过Windows下双击jmeter.bat启动工作目录通常是bin目录用命令行在别的目录启动工作目录又变了。这导致同一个脚本昨天能跑、今天不能跑根因就是启动方式变了。我的建议是写文件路径时要么用绝对路径要么用Jmeter属性动态拼接。比如把路径放在用户自定义变量里脚本里这样读import java.io.File; String baseDir vars.get(dataFileDir); File file new File(baseDir File.separator testdata.csv);另外还有一个隐藏坑System.getProperty(user.dir)返回的是启动目录不是Jmeter的安装目录。如果你需要定位Jmeter安装目录用System.getProperty(jmeter.home)这是Jmeter启动时设置的。别问我怎么知道这些坑的都是血泪。还有一个和BeanShell间接相关但特别影响排查效率的坑脚本本身有语法错误时Jmeter并不会在图形界面上弹出一个很明显的中文错误提示它只是在脚本运行的那次采样结果里显示一行异常堆栈。很多新手以为脚本没生效其实脚本直接编译失败根本没跑起来。遇到这类情况一个快速判断方法是看查看结果树里采样结果的最下方如果有BeanShellSyntaxErrorException之类的关键字那就是语法错误。把脚本逐行注释掉定位到出错行基本能解决一大半问题。6.4 JMeter自身的安装问题最后补充一个和BeanShell无关但被搜索次数极高的场景Jmeter装了但启动失败连脚本影子都看不到。通常原因集中在三个地方。一是JDK版本不对。Jmeter 5.x要求Java 8及以上有些老教程还在教Java 7照做就废了。执行java -version确认一下不要只看JAVA_HOME配置因为Path里可能覆盖了JAVA_HOME。二是Jmeter解压路径有误。不要在压缩包里直接双击运行要先完整解压。我之前遇到过用户把Jmeter压缩包拖到桌面就直接打开jmeter.bat的报错找不到lib目录就是因为没解压。三是JMeter启动闪退。Windows环境最常见的坑是PATH里没有JAVA_HOME或者JAVA_HOME配置了JRE路径而不是JDK路径。Jmeter启动脚本里会做检查如果失败会立即退出不给你任何提示。把CMD窗口留在桌面双击jmeter.bat时从同一个cmd窗口执行就能看到报错信息。把这三个问题先排掉Jmeter一定能启动起来BeanShell脚本才有运行的地方。7. 后续还能怎么玩写完第一个BeanShell脚本、跑通断言和参数传递之后这个系列还没结束。BeanShell的能力边界远不止这些我自己实际工作中还用过不少进阶玩法。比如结合JMeter属性做一个全局的测试开关。在jmeter.properties里加一个自定义属性BeanShell脚本里用props.get(ifRunFlag)去判断某个线程组要不要执行可以做成很灵活的前提条件控制。再比如用BeanShell脚本做数据生成器。性能压测时经常需要大量不同的测试数据用BeanShell写一个按规则批量生成参数的方法配合CSV或直接放入变量实测下来比手写一万行测试数据高效得多。还有一类比较进阶的用法是让BeanShell脚本读取外部的配置文件比如YAML、JSON、properties。测试脚本和测试数据分离改数据不用动脚本这套思路对团队维护测试资产特别友好。最后想说的是BeanShell脚本只是工具重要的是测试思维。脚本能帮我们完成复杂的业务校验但要不要校验、校验到哪一层还是要从接口的业务逻辑出发去设计。下一篇我打算把BeanShell脚本的常用函数库和自定义工具方法整理成一个速查手册大家如果有什么具体的脚本需求也可以带着问题来翻后面的文章。
返回列表