ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JMeter函数实战:从参数化到跨线程组传参的脚本优化指南

JMeter函数实战:从参数化到跨线程组传参的脚本优化指南 做JMeter接口测试和性能测试这些年我最大的感触是函数才是脚本里最容易被低估的东西。很多人写接口脚本动态参数要么写死要么依赖CSV文件跑单接口还行一旦落到性能压测、多用户并发、跨线程组传参脚本立刻变得僵硬错误率飙到让人怀疑人生。实际上JMeter内置了几十种函数用好了就是测试脚本里的“活数据引擎”从时间戳到随机数、从属性传参到动态断言几乎每个环节都能用函数把脚本从“死水”变成“活水”。这篇文章我打算把常用函数按使用频率拆开讲清楚每个函数的语法、参数、典型场景、常见坑以及函数和断言、Beanshell、逻辑控制器怎么搭配。内容不追求把内置函数全部罗列一遍而是把“真正常用、值得用”的讲透。无论你是刚接触JMeter还是已经在写压测脚本但经常被函数绕晕这篇应该都能帮你省下不少排查时间。1. 先从一次失败的断言说起测试脚本为何离不开函数1.1 接口测试中的变量和函数到底什么分工先弄明白一个基础问题变量和函数在JMeter里是什么关系。变量是“存值的地方”比如你在测试计划里定义一个hostapi.example.com整个测试计划都能引用函数则是“生产值的手段”比如${__time(yyyy-MM-dd)}每次调用都会动态生成一个时间字符串。在实际脚本里两者经常配合使用。你可以把函数的返回值存进变量也可以把变量作为函数的参数传进去。比如我要构造一个带时间戳的订单号可以先用${__time(yyyyMMddHHmmss)}生成当前时间再和随机数拼接到一起。少数刚入门的朋友喜欢把所有参数写死接口一旦校验唯一性就立刻失败另一些人又过度依赖CSV文件做参数化每加一个字段就要维护一列数据其实很多动态数据用函数一条表达式就搞定了。我给出的经验是能通过函数“算出来”的数据就不要用文件去维护只有那些需要业务上预先定义、确实需要一一对应的数据比如一批测试账号、一组商品ID才值得走CSV参数化。函数负责“即时生成”CSV负责“按需读取”分工清楚了脚本逻辑才会干净。1.2 性能压测中函数的价值场景模拟靠它撑起来接口测试阶段函数更多是解决“参数不能写死”的问题到了性能压测阶段函数的意义就完全不一样了。压测的核心是模拟大量虚拟用户同时操作服务端如果所有虚拟用户提交的请求参数一模一样那测出来的结果基本没有参考价值。举个例子注册接口一般会校验手机号是否重复你用固定手机号并发200个用户大概率只能测出“这个手机号到底重不重复”这一个结果而服务端真实的注册处理能力、数据库写入瓶颈、缓存策略完全体现不出来。函数在压测中的作用就是让每个虚拟用户拿到“看起来像是独立真实用户”的数据。随机用户名、随机手机号、随机邮箱、动态时间戳、唯一订单号这些全部可以靠函数组合实现。还有一个容易忽视的点压测时服务端往往会根据请求来源做缓存或限流如果所有并发请求的User-Agent、Token、业务ID都相同服务端可能直接命中缓存或直接拒绝最终报告里的高吞吐量其实是“缓存撑起来的假象”和你想要的真实容量评估完全是两回事。1.3 必须先弄清的JMeter函数通用语法使用函数之前通用语法必须先刻在脑子里${__funcName(参数1,参数2,...)}。注意这里有几个细节很容易踩坑。第一函数名前面是两个下划线不是变量引用。变量引用是${varName}单层写法函数则是${__xxx(...)}。很多报错排查到最后发现只是下划线少打了一个。第二参数之间的逗号是英文逗号。如果你要传的值本身包含英文逗号需要用\转义或者调整写法避免直接传逗号。第三部分函数支持“默认值”参数写空也要保留逗号位置。比如${__P(token,)}第二个参数留空代表取不到属性时返回空字符串这个逗号不能省。JMeter自带的函数助手对话框Tools Function Helper Dialog可以帮你选择函数并生成表达式但要注意它的作用只是“复制表达式文本”并不会自动把表达式塞进你的输入框生成之后还需要手动粘贴。很多人第一次用的时候点了Generate没反应以为是功能坏了其实就是这个原因。2. 高频函数逐个拆时间、随机、计数、字符串四类最常用2.1 时间函数三兄弟__time、__timeShift、__dateTimeConvert时间类函数是接口测试里出现频率最高的一类因为几乎所有业务接口都要和时间打交道订单创建时间、日志时间、签名过期时间、查询起止时间。${__time(,)}返回的是当前时间的毫秒时间戳注意第一个参数留空时返回的是13位毫秒时间戳如果传格式字符串则返回格式化时间。比如${__time(yyyy-MM-dd HH:mm:ss)}返回2025-01-08 14:30:00这样的字符串。还有一个常见用法是取秒级时间戳${__time(/1000,)}把毫秒时间戳直接除以1000在很多后端签名逻辑里要求的往往是10位秒级时间戳这个技巧很实用。__timeShift是另一个高频函数用来做时间的加减运算。语法是${__timeShift(格式,日期,偏移量,区域)}日期留空表示基于当前时间。这里必须提醒一个特别容易踩的坑偏移量用的是ISO 8601格式PT5M才是5分钟如果写成P5M那是5个月。我曾经在压测脚本里想生成“5分钟前”的时间写成了P5M结果整批数据的时间字段全部晚5个月服务端直接拒绝了排查了很久才发现问题。常用写法参考PT30S是30秒PT1H是1小时P2D是2天P1W是1周。__dateTimeConvert做的是格式转换典型场景是接口返回的时间格式和请求需要的格式不一致。比如查询接口返回了2025-01-08 14:30:00但下一个接口要求传的是20250108143000一条${__dateTimeConvert(2025-01-08 14:30:00,yyyy-MM-dd HH:mm:ss,yyyyMMddHHmmss,)}就能解决。注意时区参数和默认时区的问题日期转换的时区逻辑如果不明确建议先在某一个取样器中用Debug Sampler打印出来看看结果再决定要不要加时区参数。2.2 随机和唯一性函数__Random、__RandomString、__UUID、__threadNum随机类函数是构造独立用户数据的核心工具。${__Random(1,1000,)}生成指定范围内的随机整数闭区间包含上下限。注册接口生成手机号的时候我一般用138${__Random(10000000,99999999,)}前缀加8位随机数比直接${__Random(10000000000,99999999999,)}更符合手机号段规律也更容易被服务端校验通过。${__RandomString(8,abcdefghijklmnopqrstuvwxyz0123456789,)}生成指定长度的随机字符串第二个参数是字符集。比如生成测试邮箱user_${__RandomString(6,abcdefghijklmnopqrstuvwxyz,)}example.com基本可以保证并发压测时不重复。${__UUID()}生成标准UUID字符串几乎没有重复可能适合做订单号、交易号、文件名等强唯一性字段。不过要注意UUID是36位含连字符的完整格式如果你只是需要一个不重复的短标识用__RandomString会更合适请求体里塞一个大UUID有时候纯粹增加无谓的带宽。${__threadNum()}返回当前线程编号在压测场景里特别有用。排查错误响应时能在请求参数里看到线程编号配合日志就能快速定位是哪些虚拟用户报错。比如压测时把${__threadNum()}拼在用户名后缀里user_${__threadNum()}_${__time(MMddHHmmss)}既能保证部分唯一性又能在结果树里直接看到是第几个线程发起的请求。2.3 计数器函数__counter和迭代次数的使用边界${__counter(TRUE,)}和${__counter(FALSE,)}都是递增计数器区别在于作用域。TRUE是进程内全局计数多个线程公用一个计数序列FALSE是每个线程独立计数每个线程自己从1开始。为了说清楚假设5个线程、每个线程循环2次。用TRUE计数序列是1、2、3、4、5、6、7、8、9、10全局不重复用FALSE计数每个线程内部两次循环是1、25个线程各自都是1、2。这里还要和${__iterationNum}做一个区分。__iterationNum反映的是当前线程的当前循环次数在同一线程内和__counter(FALSE)的值往往一致但它更偏向“循环次数”的语义。什么时候必须用__counter(TRUE)需要全局流水号的时候比如生成一批全局订单编号要保证压测进程内完全不重复用TRUE模式最直接。需要注意跨压力机场景下TRUE只保证单机内不重复分布式压测时还是要靠随机函数或外部数据保证全局唯一。2.4 字符串与编码类函数__char、__unescape、__escapeHtml这一类函数在新手脚本里出现频率不算高但遇到边界情况时非常省事。${__char(72,69,76,76,79)}可以把一组Unicode码点转成对应字符比如这几个数字转出来就是HELLO。接口测试中有些参数需要输入特殊字符比如换行符、中文符号在请求体里直接粘贴可能造成编码混乱用__char生成更可控。${__unescape(...)}用来反转义HTML实体。比如接口返回了lt;scriptgt;你想断言它是否等于script直接写死的断言文本怎么都不匹配此时把响应值过一遍__unescape再比就对了。${__escapeHtml(...)}则是反向操作把HTML特殊字符转义成实体。这类函数整体使用率不高但值得知道存在因为你不知道哪一天接口测试中就会蹦出一个编码相关的怪问题。真遇到的时候知道有现成函数可以用比临时去写JSR223脚本高效得多。函数核心作用常用示例典型场景__time生成时间戳/格式化时间${__time(yyyy-MM-dd HH:mm:ss,)}请求时间字段、订单号拼接__timeShift时间加减${__timeShift(yyyy-MM-dd HH:mm:ss,,PT5M,)}5分钟前的查询时间__Random生成随机整数${__Random(1000,9999,)}动态数值参数__RandomString生成随机字符串${__RandomString(6,abc123,)}随机用户名、验证码__UUID生成UUID${__UUID()}订单号、唯一标识__counter递增计数${__counter(TRUE,)}全局唯一流水号__threadNum当前线程编号${__threadNum()}压测日志定位3. 跨线程组传参的三件套__P、__setProperty与__V的实战3.1 变量与属性两个容易混淆的数据域很多人在JMeter里做跨线程组传参时都会遇到一个现象在线程组A里成功提取了token在线程组B里用${token}取值发现是空的。原因在于JMeter的“变量”是线程级的每个线程组、每个线程都有自己独立的变量副本线程组之间互不可见。和变量对应的是“属性”属性是JVM级别的全局共享理论上任何线程组都能读取。打个比方变量是每个人工位上的便利贴随手写随手用别人看不到属性是公司公共公告栏谁写谁都能看但需要主动去读。跨线程组传参这件事本质就是“从便利贴搬到公告栏再在另一个工位去读公告栏”。3.2 登录token从线程组A传到线程组B的操作套路令牌传递是压测脚本里最典型的跨线程组场景先在一个线程组里登录获取token然后在另一个线程组里用这个token去调业务接口。标准做法分三步走。第一步在登录线程组的HTTP请求上添加JSON提取器或正则提取器把返回的token存到变量里比如token_var。第二步添加一个JSR223后置处理器里面写一行props.put(token, vars.get(token_var))把变量转成属性。第三步在目标线程组的请求头或参数里用${__P(token,)}读取属性。为什么不直接在第二步用${__setProperty(token,${token_var},)}函数式写法在简单场景下可行但一旦token值里包含特殊字符、逗号或者表达式解析顺序出现问题取出来的值很容易残缺或报错。JSR223脚本用props.put是最直接的、也是我长期在各种项目中验证过的、最不容易出问题的写法。Groovy脚本里能感知到性能上的差异JSR223的存取效率比函数解析更高压测环境里尤其明显。整个链路里最容易出错的节点是启动顺序。JMeter默认按“线程组在测试计划中的排列顺序”启动线程组如果业务线程组写在了登录线程组前面token还没生成就被读了自然为空。所以日志中如果出现“token取不到”的报错先检查线程组顺序再看属性名拼写最后再查后置处理器是否真的执行了。3.3 嵌套变量求值__V和__eval的实用场景${__V(...)}是一个容易被忽视但非常有用的函数作用是对变量名做二次求值。什么意思常规写法${user_1}是直接取user_1这个变量的值假设我们有一组变量user_1、user_2、user_3想根据当前循环序号动态取其中一个直接写${user_${i}}是没法解析的因为JMeter不支持这种嵌套引用。用${__V(user_${i})}就可以实现动态拼变量名后取值。我第一次用这个函数是在做多账号轮询从登录接口提取了10个用户的凭证分别存为cred_1到cred_10业务线程里每次循环用${__V(cred_${__counter(FALSE,)})}取出当前凭证。这样每个虚拟用户可以在自己的线程内逐步轮换账号既复用了测试数据又避免了多线程抢同一份变量。__V还有一个常见用法是配合CSV文件实现“数据轮询”。很多人不知道CSV文件读取后得到的变量可以按命名规则存多组再用__V动态拼索引比每次从文件里逐行读取灵活得多。3.4 属性设置时机setProperty函数的边界与JSR223的取舍__setProperty函数本身可以用来把变量写入属性但它有一个比较明显的限制函数在取样器执行时才会被求值而且它的返回值本身也会被处理为字符。如果你在某个取样器的参数里写${__setProperty(token,abc,)}JMeter会先执行属性设置再用函数的“空值”作为参数填充。这个行为容易让参数值变成空白甚至引起后续请求失败。更可靠的策略是把属性写入操作放到JSR223后置处理器里用Groovy脚本的props.put(key, value)完成读取属性则在需要的地方用${__P(key,默认值)}。这样职责清晰写入用脚本读取用函数。大规模并发时JSR223的性能优势也更明显函数虽然写起来短但JMeter每次解析函数表达式都有额外开销热点路径上能省一点是一点。另外要记住属性是存储于当前JVM的分布式压测时负载机的属性并不互通每台机器有各自的属性池。如果你要跨负载机传递数据属性这条路走不通得借助外部中间件或参数文件。4. 函数与断言、逻辑控制、脚本协同别只会拼参数4.1 断言里的函数两个时间戳如何比大小常规的“响应断言”只能做文本匹配它没法比较数字大小。可实际接口测试里经常要断言“接口处理耗时是否小于5000毫秒”“返回的时间戳是否晚于当前时间”“分页的总页数是否大于等于10”。这些场景下函数是响应断言的得力助手。比较两个时间戳大小一种常见做法是把时间值提取到变量然后用${__jexl3(...)}函数做逻辑判断。比如想判断“响应里的时间戳是否晚于当前时间”可以用${__jexl3(Long.parseLong(vars.get(respTimestamp)) ${__time(,)},)}。这个表达式里嵌套了两个函数外层__jexl3负责按Java语法计算布尔值内层__time负责生成当前毫秒时间戳。这类嵌套写在响应断言的“匹配测试”里勾选“字符串”类型后函数会返回true或false和期望值true比对即可通过断言。这个方案能用但可读性确实不高表达式一旦超过三层嵌套后面维护的人基本看天书。所以我的建议是简单的比较逻辑用函数稍微复杂一点就上JSR223断言里面写Java/Groovy原生的条件判断逻辑清晰、报错定位快。函数不是不能做而是要看值不值。4.2 逻辑控制器里的函数随机分流与动态思考时间接口测试和性能测试里函数还可以直接驱动逻辑流程而不只是提供参数值。随机分流是一个很典型的玩法。假设压测目标是模拟用户行为70%的用户走“搜索商品”流程30%的用户走“下单”流程可以用一个${__Random(1,100,)}生成随机数再结合Switch控制器做分支选择。Switch控制器可以通过变量值决定执行哪个子逻辑给每个分支编号1到100数值落在1到70走搜索71到100走下单就能在压测中模拟出按比例分发请求的效果。另一个非常实用的场景是动态思考时间。性能测试中“思考时间”如果全部固定会导致虚拟用户行为过于一致全部用户在同一时刻发请求测出的峰值明显偏高、整体曲线失真。正确的做法是让思考时间落在合理范围内${__Random(2000,5000,)}就是一个直接可用的随机化方式。把它填到“固定吞吐量定时器”或“常数吞吐量定时器”的延迟字段里每个虚拟用户在两次请求之间会随机等待2到5秒比固定等待更加贴近真实用户的操作节奏。4.3 函数与Beanshell的边界哪些场景别硬用函数说一句扎心的实话过度使用函数会让脚本变成一团乱麻。函数的定位是“快速生成一个值”或“做一个简单的表达式判断”它不适合处理复杂业务逻辑。举几个典型的例子。带签名的接口请求体要求signMD5(appidtimestampsecret)用函数写这种签名计算会非常痛苦你需要在请求参数里嵌套多层表达式而且中间任何一步出问题都不容易定位这种场景就应该用JSR223前置处理器写一段Groovy脚本读参数、拼字符串、算MD5、存变量一步到位。需要做正则匹配或者从响应JSON中提取多个值再二次加工时也不建议硬用函数。正则提取器提取出来的结果要再判断、再遍历这些是脚本的工作不是函数的工作。判断边界其实很简单如果一个表达式需要超过两层嵌套或者需要循环、条件分支、正则匹配就直接写JSR223如果只是生成一个随机数、取一个时间戳、读一个属性那函数是最轻量可靠的选择。写JSR223脚本还有一个必须注意的点脚本本身要避免副作用。脚本中定义的临时变量名不要和环境中的变量冲突vars、props、log这些内置对象用熟了基本就能避开90%的坑。4.4 正则提取与函数的互补提取结果再加工正则表达式提取器和JSON提取器负责“从响应里取数据”函数负责“对取到的数据做变换”它们天然就是一对。最简单的互补用法提取响应里的某个ID再用__RandomString给它拼一个后缀生成新的追踪编号。比如从查询接口提取order_id然后在下一个接口里构造${order_id}_${__RandomString(4,abcd1234,)}作为新的退款单号。稍微进阶一点的玩法是用__V配合提取器实现“列表轮询”。JSON提取器可以一次性提取多个匹配值并生成item_1、item_2、item_3这样的变量组。后续请求里要轮流使用这些值就可以在循环控制器里用${__V(item_${__counter(FALSE,)})}依次引用。这个组合能应付大多数“从列表中选择一个元素作为下一步请求参数”的需求很多JMeter封装框架里所谓的数据驱动本质上也就是这一套。5. 函数报错排查实录这五个坑我基本都踩过5.1 函数写了却不生效三种典型的根因函数没有求值、原样出现在请求体里是最常见的报错样式。比如请求参数变成了mobile138${__Random(10000000,99999999,)}这种字面文本而不是真的随机数。第一种根因是函数名拼写错误或者下划线数量不对。${__time(...)}少了一个下划线变成${_time(...)}JMeter会按普通变量处理自然原样输出。第二种根因是括号没有闭合或者参数位置错乱特别是函数嵌套时少写了一个右括号解析器直接放弃求值。第三种根因是函数返回了空值而空值恰好被写进了必填参数接口报错后回看请求表面看是参数格式问题实际是函数压根没有生成任何内容。排查这类问题推荐在任何取样器之前放一个Debug Sampler执行之后能在查看结果树里看到所有变量、属性和JMeter内部变量函数到底解析成了什么值一目了然。强烈建议批量排查时不要肉眼看请求体直接用Debug Sampler输出省时省力。5.2 跨线程组属性偶发为空别只盯着setProperty本身属性在后置处理器里可能执行了、日志里也可能没有报错但目标线程组里读取时依然是空。这类问题最容易让人在原地打转因为“写入”和“读取”两边看起来都是对的。其实有几个隐藏原因值得优先排查。第一个是线程组启动顺序测试计划里线程组A写在下面、线程组B写在上方默认启动时B先跑自然会读到空值。第二个是属性名大小写props.put(token, ...)和${__P(Token,)}读的不是一个东西。第三个是时间差线程组B可能在某些场景下和A并行启动此时登录请求还没执行完token也还没写入B就已经读取完了。解决思路也简单先在读取端用带默认值的写法${__P(token,__NOT_FOUND__)}如果默认值都输出了说明属性真的没写进去接着在写入端加一个日志输出log.info(props.get(token))验证写入端确实执行了。用这种“逐步缩小范围”的方式基本几分钟就能定位是哪一段出了问题。5.3 函数嵌套过深可读性崩了怎么重构不破坏逻辑三层甚至四层函数嵌套在一个表达式里写的时候可能还能理解过两个星期再看基本等于看加密文本。比如${__jexl3(Long.parseLong(vars.get(respTimestamp)) ${__time(,)},)}这种虽然功能没问题谁都会在维护时头疼。重构的原则是“把中间结果拆出来”。如果某个函数的结果后面要多次使用先定义成变量添加一个“用户自定义变量”把${__time(yyyyMMddHHmmss)}存为curTime后续表达式直接用${curTime}。类似的接口请求体非常长的时候可以把请求体会话模板单独拆到一个“用户自定义变量”里再按组成部分拼接。这样虽然多创建了几个变量但脚本的可读性和可维护性会好非常多。另外一个实践技巧是在脚本的关键位置加注释说明。JMeter里每个测试元件都有注释区域给复杂的函数表达式写一行说明比如“这里生成的是5分钟前的时间格式为yyyyMMddHHmmss”看起来是小事但团队协作时能省下大量沟通成本。5.4 压测时函数的性能开销并发场景下的实测观察函数虽然方便但也有代价。JMeter放大压测时每个虚拟用户每次请求都会解析函数表达式这个解析过程是纯CPU开销。我自己在1000并发场景下实测过脚本里大量使用__UUID和__RandomString生成唯一标识时压力机本身的CPU占用会比采用预置数据文件高出不少在普通配置的机器上甚至会反过来成为瓶颈。所以我的建议是分层处理低并发阶段函数随意用高并发压测能预生成的参数尽量提前在测试数据文件里生成好用CSV直接读取只在必须动态生成的地方比如时间戳保留函数。另外一个降低开销的实用做法是对不要求绝对唯一、只要求“足够随机”的字段缩小随机范围、缩短随机字符串长度这些操作都能明显减少函数的计算量。压测的本质是测服务端容量不是测客户端脚本表达式的解析性能别让脚本自身开销干扰了测试结果。6. 全链路案例注册、登录、业务请求里的函数组合6.1 场景设计三个线程组怎么编排接下来用一个贯穿全篇的案例把上面提到的函数和技巧串起来。目标是构建一条完整的业务链路注册新用户、登录获取token、携带token调用核心业务接口。我设计了三个线程组。第一个线程组是“注册新用户”生成随机账号并提交注册请求第二个线程组是“登录并提取token”用注册成功的账号登录第三个线程组是“核心业务请求”把token放到请求头里发起真实业务操作。实际压测时注册和登录可以用较低并发执行核心业务线程组才是高并发的重点。这样做的好处是注册和登录接口的请求量被控制住不会出现“因为注册接口被限流”导致后续业务全部无法执行的情况同时核心业务线程组可以独立调整并发数测试目标更聚焦。6.2 注册接口随机手机号和时间戳组合生成注册接口的请求体核心字段我推荐这么构造{ mobile: 138${__Random(10000000,99999999,)}, name: test_${__RandomString(6,abcdefghijklmnopqrstuvwxyz,)}, timestamp: ${__time(yyyyMMddHHmmss)}, source: jmeter }手机号用138前缀加8位随机数基本合规且不会重复用户名用固定前缀加随机字母时间戳用格式化后的当前时间。这样一个注册请求的参数每次执行都不一样重复提交不会撞到唯一性约束。这里要提一个断言设计细节注册接口返回的内容里可能包含新用户ID而登录接口正好需要这个ID作为用户名所以注册请求后面要加JSON提取器把用户ID存到变量里供后续请求使用。如果注册接口本身返回了用户ID这一步不能省否则登录环节还得再改参数链路就断了。6.3 登录后token回传属性跨线程组传递完整配置登录线程组的第一步是使用前面注册好的用户名密码发起登录请求。登录响应里通常有一个token字段用JSON提取器提取为login_token变量。接着添加JSR223后置处理器props.put(token, vars.get(login_token));然后在第三个线程组的HTTP请求头中添加Authorization: Bearer ${__P(token,__TOKEN_MISSING__)}默认参数我特意写成__TOKEN_MISSING__一旦token没传对响应结果里能非常明显地看到这个字符串排查效率比空值高得多。整个传递过程中线程组顺序一定不能乱注册、登录、业务依次排列。执行前可以先用1个线程、1次循环跑通再放大并发。6.4 并发压测下的响应断言与错误定位业务线程组里加上响应断言和错误定位逻辑。假设业务接口要求响应码200且业务耗时小于5000毫秒断言可以这么配先用JSON提取器提取响应里的code和elapsed然后用__jexl3做复合判断。不过更实际的做法是直接在JSR223断言里写def code vars.get(code); def elapsed Long.parseLong(vars.get(elapsed)); if (!200.equals(code) || elapsed 5000) { AssertionResult.setFailure(true); AssertionResult.setFailureMessage(响应异常code code , elapsed elapsed); }这样断言失败时会在结果树里直接看到失败原因而不是一个简单的“文本不匹配”。同时请求参数里如果带了${__threadNum()}失败日志里就能精确追溯到是第几个虚拟用户的请求出了问题定位效率成倍提升。6.5 压测结果怎么看聚合报告里的关键指标压测跑完之后聚合报告和汇总报告里的几个指标要会看。样本数确认请求是否全部执行平均响应时间是最直观的参考但不要只看平均值要看90%或95%的响应时间后者才代表绝大多数用户的真实体验吞吐量每秒请求数是服务端处理能力的核心指标错误率只要不为零就要逐条去看失败原因是断言失败还是连接超时。还要提醒一点错误率要区分“业务失败”和“脚本失败”。业务失败是接口返回的业务码不对比如注册成功但返回了用户已存在脚本失败是参数写错、token没传上、断言表达式有误。压测报告里如果混着一堆脚本造成的失败容量评估就不可信。我每次压测完的第一件事是先抽几条失败样本打开响应体确认失败原因属于哪一类再决定这轮结果能不能用。最后再说一个压测端的小建议正式压测之前永远先用低并发跑一遍全流程确认参数生成、token传递、断言逻辑全部正常再逐步加大并发。函数表达式写错时低并发下就能暴露而高并发下所有问题都会被放大到难以排查。做好这一步比带多少参数、写多少函数都重要。
返回列表