性能测试脚本的编写一直是测试团队里那块"食之无味、弃之可惜"的硬骨头。业务迭代快的时候,接口一天改三版,脚本跟着改到怀疑人生;团队里会写JMeter脚本的人就那么两三个,需求一堆积,排期直接爆炸。最近半年我一直在折腾用Agent配合大模型来做性能测试脚本的自动化生成,从最初的"玩具级Demo"到如今能在真实项目里跑通全流程,中间踩的坑比想象中多得多。这篇就把整套思路、关键实现、以及那些文档里不会写的经验一次性摊开讲清楚,适合已经会用JMeter做基础压测、想进一步提效的测试同学,也适合对Agent落地感兴趣但还没找到具体场景的开发者。
1. 为什么性能测试脚本生成值得用Agent来做
1.1 传统脚本编写的三个死结
先说清楚痛点,不然容易陷入"为了用Agent而用Agent"的陷阱。性能测试脚本和普通功能测试脚本最大的区别在于:它需要模拟真实用户的行为链路,涉及参数关联、思考时间、并发梯度、断言校验等多个维度。传统做法里,一个中等复杂度的下单链路脚本,从抓包分析到调通,熟练工也得花上大半天。
第一个死结是接口变更的传导成本。后端加了个签名字段,或者把某个参数从query挪到了body,脚本就得跟着改。一个项目里几十个脚本,改一遍就是一天。第二个死结是知识门槛。JMeter的BeanShell、JSR223、正则提取器、JSON提取器这些组件,新手看着就头大,团队里能独立写复杂脚本的人永远是稀缺资源。第三个死结是重复劳动。登录、鉴权、公共参数构造这些逻辑,每个脚本都要抄一遍,抄错一个字符就调半天。
1.2 Agent相比纯大模型生成的优势在哪
很多人第一反应是"直接让大模型写JMeter脚本不就行了"。我试过,效果不稳定。纯大模型生成的问题在于:它不知道你的接口真实长什么样,不知道你的环境地址,不知道你的参数关联规则,生成出来的东西看着像那么回事,一跑全是错。
Agent的价值在于它能把"生成"这件事拆成多个可执行的步骤,并且每一步都能调用工具去获取真实信息。比如它可以先调用工具去读取接口文档或者抓包文件,解析出接口的URL、方法、参数结构;再调用工具去查询历史脚本库,找到相似的脚本作为参考;最后才让大模型基于这些真实上下文来生成脚本。这个"感知-决策-执行"的循环,才是Agent区别于一次性Prompt调用的核心。
我实测下来,纯大模型生成的脚本一次通过率大概在30%左右,而引入Agent流程后,一次通过率能到75%以上,剩下的25%基本是环境配置或者特殊业务逻辑的问题,人工微调几分钟就能搞定。
1.3 适合切入的场景判断
不是所有场景都值得上Agent。我的经验是,满足以下条件的场景优先做:接口数量多且结构规整(比如RESTful风格)、有相对稳定的接口文档或抓包数据、脚本模板重复度高、团队有JMeter使用基础。反过来,如果接口协议特别冷门(比如自定义的二进制协议)、业务逻辑极度复杂(涉及大量加密和动态令牌),那Agent生成的脚本可能还不如手写快,这时候更适合做"半自动"——让Agent生成骨架,人工填充核心逻辑。
2. Agent生成JMeter脚本的整体架构拆解
2.1 四个核心模块的职责划分
整套系统我拆成了四个模块,各司其职,避免耦合太深导致调试困难。
输入解析模块负责把各种来源的接口信息统一成结构化数据。来源可能是Swagger文档、Postman导出的JSON、HAR抓包文件,甚至是手写的接口说明。这个模块的输出是一份标准化的接口描述,包含URL、方法、请求头、请求体结构、响应结构、参数关联关系。
知识检索模块负责从历史脚本库和接口文档库里找到与当前需求最相似的参考。这里我用的是向量检索,把历史脚本的接口路径、参数名、业务描述做embedding,存到向量库里,生成新脚本时先检索Top-K相似的作为few-shot示例。
脚本生成模块是核心,它接收结构化接口描述和检索到的参考示例,通过精心设计的Prompt让大模型输出JMeter的JMX文件内容。这里有个关键点:不要让大模型直接输出XML,而是让它输出一个中间DSL(领域特定语言),再由代码把DSL转成JMX。这样做的原因是XML的标签嵌套太深,大模型很容易写错闭合标签,而DSL可以设计得非常简洁,出错率大幅降低。
校验修复模块负责对生成的脚本做静态检查和动态试跑。静态检查包括XML格式校验、必填字段检查、参数引用检查;动态试跑则是把脚本丢到测试环境跑一遍单线程,看是否能通。如果失败,把错误信息回传给大模型让它修复,形成一个闭环。
2.2 为什么选择JMeter作为目标格式
热词里JMeter出现频率很高,这符合实际。JMeter虽然界面丑、启动慢,但它的生态确实成熟:插件丰富、分布式压测方案成熟、报告体系完善、团队接受度高。而且JMX文件本质是XML,结构规整,非常适合程序化生成。
对比之下,如果用k6或者Locust,虽然脚本本身就是代码、生成起来更自然,但团队迁移成本高,而且很多公司的压测平台就是基于JMeter搭的,生成的脚本要能直接导入平台才有价值。所以我的选择是:以JMeter为主,同时保留扩展到其他格式的能力。
2.3 中间DSL的设计思路
DSL的设计直接决定了生成质量。我的DSL大概长这样:
scenario: 下单链路 threads: 100 rampup: 60 duration: 300 steps: - name: 登录 method: POST path: /api/login headers: Content-Type: application/json body: username: ${username} password: ${password} extract: - name: token from: $.data.token type: json assert: - field: $.code operator: equals value: 0 - name: 创建订单 method: POST path: /api/order headers: Authorization: Bearer ${token} body: productId: ${productId} quantity: 1 assert: - field: $.code operator: equals value: 0这个DSL的好处是:结构扁平、语义清晰、大模型容易生成、代码容易解析。从DSL到JMX的转换逻辑我写了一个转换器,把每个step映射成JMeter的HTTPSampler,extract映射成JSON提取器,assert映射成响应断言。转换器是纯代码逻辑,不依赖大模型,所以稳定性有保障。
3. 从接口文档到可执行脚本的完整链路
3.1 接口信息的结构化提取
这一步是整个链路的地基。我处理过三种来源的接口信息,各有各的坑。
Swagger文档是最理想的,直接解析JSON就能拿到结构化的接口定义。但要注意,很多团队的Swagger维护得并不及时,字段类型和实际不符的情况很常见。我的做法是解析完Swagger后,再用实际抓包数据做一次交叉验证,不一致的地方以抓包为准。
HAR抓包文件是第二常用的来源。用Chrome开发者工具或者Charles抓包后导出HAR,里面包含了完整的请求响应。解析HAR的难点在于:一个页面可能触发几十个请求,需要过滤出真正的业务接口,排除掉静态资源、埋点、监控这些噪音。我的过滤规则是:排除Content-Type为image/css/js的请求,排除URL里包含analytics、track、monitor的请求,剩下的按域名和路径前缀聚类。
手写接口说明是最麻烦的,但也是最常见的。很多老系统的接口文档就是Word或者Confluence页面。这种情况我会让大模型先做一轮信息抽取,把非结构化的文本转成结构化JSON,然后人工核对一遍。这里要强调:大模型抽取的结果必须人工核对,尤其是参数类型和必填项,错了会导致后续全盘皆输。
3.2 参数关联关系的自动识别
参数关联是性能测试脚本的灵魂。登录拿到的token要传给后续接口,创建订单拿到的orderId要传给支付接口,这些关联关系如果识别错了,脚本跑起来就是一堆401和404。
我的识别策略分三层。第一层是命名匹配,如果前一个接口的响应字段名和后一个接口的请求参数名相同或高度相似(比如token和accessToken),就建立关联。第二层是值匹配,在试跑阶段观察实际的值传递,如果某个响应值出现在了后续请求里,就建立关联。第三层是大模型推断,把接口列表和业务描述丢给大模型,让它推断哪些接口之间存在数据依赖。
三层结合起来,识别准确率能到90%以上。剩下的10%通常是业务逻辑特别隐晦的关联,比如某个接口需要传一个时间戳,而这个时间戳是另一个接口返回的。这种只能靠人工补充规则。
3.3 生成结果的静态校验清单
脚本生成出来后,不能直接跑,先过一遍静态校验。我整理了一份检查清单,每次生成后自动执行:
| 检查项 | 检查内容 | 失败处理 |
|---|---|---|
| XML格式 | JMX文件是否能被XML解析器正常解析 | 重新生成 |
| 必填字段 | 每个Sampler是否有name、path、method | 自动补全默认值 |
| 参数引用 | 所有${var}形式的变量是否都有定义 | 标记为待人工确认 |
| 提取器配置 | JSON提取器的表达式是否合法 | 重新生成该提取器 |
| 断言配置 | 断言字段路径是否合法 | 重新生成该断言 |
| 线程组配置 | 线程数、rampup、duration是否合理 | 按默认值填充 |
这份清单帮我拦下了大概40%的低级错误,大大减少了动态试跑的次数。
3.4 动态试跑与错误回传修复
静态校验过了之后,把脚本丢到测试环境跑单线程。这里有个细节:一定要用单线程跑,多线程跑的话错误信息会混在一起,很难定位。跑完之后收集结果,如果有失败,把失败请求的URL、请求体、响应体、错误信息打包,回传给大模型让它修复。
修复的Prompt要设计得具体,不能只说"这个脚本报错了,帮我修一下"。我会把原始DSL、生成的JMX片段、错误信息、以及相关的接口文档一起给大模型,让它输出修正后的DSL片段。实测下来,大部分错误(比如参数名写错、路径拼错、断言条件不对)一轮修复就能解决,少数需要两到三轮。
4. 实操中踩过的坑与应对方案
4.1 大模型生成的JSON提取器表达式频繁出错
这是最高频的问题。JMeter的JSON提取器用的是JsonPath语法,大模型经常把$.data.token写成$data.token或者$.data[token]。这种错误静态校验能发现,但每次都要重新生成很浪费时间。
我的解决方案是在Prompt里内置一份JsonPath语法速查表,并且给出正反例。更重要的是,我在DSL层面做了约束:extract的from字段只允许$.开头的标准JsonPath,生成后先用代码校验一遍语法,不合法就直接报错让大模型重写,而不是等到JMeter跑起来才发现。
4.2 参数关联在并发场景下的失效
这个问题很隐蔽。单线程跑的时候一切正常,一上并发就大量失败。排查了半天才发现,是某个接口的响应里返回了一个全局唯一的ID,脚本把它提取出来存到了线程共享的变量里,并发的时候互相覆盖了。
JMeter的变量作用域是个容易踩的坑。${var}这种写法默认是线程级别的,但如果你用了__setProperty或者BeanShell的全局变量,就会变成进程级别。我的做法是:所有提取出来的变量都用线程级作用域,需要跨线程共享的数据(比如全局配置)才用属性。生成脚本时,提取器统一配置为线程级,避免这个问题。
4.3 思考时间与并发梯度的合理设置
大模型生成脚本时,如果不给约束,它会把思考时间设成0,rampup设成1秒,线程数设成1000。这种配置一跑就把测试环境打挂了,还会被运维找上门。
我在DSL里加了默认值约束:思考时间默认1-3秒随机,rampup默认等于线程数的一半,duration默认300秒。同时在Prompt里明确告诉大模型:这是性能测试脚本,不是压力测试脚本,配置要贴近真实用户行为。如果确实需要极限压测,人工再调整。
4.4 环境地址和敏感信息的处理
生成的脚本里不能硬编码环境地址和账号密码。我的做法是在JMX里用${__P(host)}这种属性引用的方式,把环境相关的配置外置到命令行参数或者配置文件里。生成脚本时,所有环境相关的值都写成属性引用,实际执行时通过-Jhost=xxx传入。
敏感信息比如密码,绝对不能出现在生成的脚本里。我的处理是:脚本里用占位符,实际执行时从密钥管理服务或者环境变量里读取。这一点在团队协作时尤其重要,脚本要进代码仓库的,硬编码密码就是安全事故。
4.5 生成脚本的可维护性
自动生成的脚本如果结构混乱,后续人工维护会很痛苦。我在生成时强制了几个规范:每个Sampler的name必须包含业务含义(比如"登录-获取token"而不是"HTTP Request 1"),相关的Sampler用Simple Controller分组,注释里写明接口的业务说明。这些规范让生成的脚本看起来像是人写的,团队其他成员接手时不会一脸懵。
5. 效果验证与持续优化
5.1 用真实项目数据做回归测试
我拿了一个真实项目的30个接口做验证,覆盖登录、查询、下单、支付四条链路。第一轮生成,22个脚本一次通过,5个需要一轮修复,3个需要人工介入。人工介入的三个分别是:一个用了自定义加密算法、一个涉及文件上传、一个接口文档严重过时。
经过两轮Prompt优化和DSL调整后,第二轮生成的一次通过率提升到了26个,剩下4个需要一轮修复。这个提升主要来自参数关联识别准确率的提高和JsonPath语法约束的加强。
5.2 建立脚本质量评分机制
光看通过率不够,还要看脚本质量。我建了一个简单的评分机制,从五个维度打分:功能正确性(能否跑通)、参数关联完整性(关联关系是否都识别了)、配置合理性(线程数、思考时间是否合理)、可维护性(命名、分组、注释是否规范)、性能开销(生成的脚本本身是否引入了不必要的开销)。
每个维度1-5分,总分25分。低于18分的脚本打回重新生成。这个机制帮我发现了一些"能跑通但质量差"的脚本,比如参数关联靠硬编码而不是提取器、断言只检查HTTP状态码不检查业务码。
5.3 Prompt迭代的经验
Prompt优化是个持续的过程。我总结了几个有效的技巧:一是给正反例,告诉大模型什么样的输出是好的、什么样是坏的;二是分步骤引导,不要让它一次性生成整个脚本,而是先让它分析接口依赖关系,再生成单个接口的脚本,最后组装;三是错误案例库,把历史修复过的错误整理成案例,在Prompt里作为"避免这些错误"的参考。
还有一个反直觉的经验:Prompt不是越长越好。我一开始把所有的规则、示例、约束都塞进去,结果大模型反而抓不住重点。后来精简到核心规则加三五个高质量示例,效果反而更好。
5.4 后续可扩展的方向
目前这套系统主要解决的是"从接口文档到JMeter脚本"的生成。后续我想扩展的方向有几个:一是支持从性能测试需求直接生成场景(比如"模拟1000用户抢购"直接生成对应的脚本);二是支持脚本的自动优化(比如根据历史压测结果自动调整线程数和思考时间);三是支持多格式输出(除了JMeter,还能生成k6、Locust的脚本)。
另外一个有意思的方向是把Agent用到压测结果分析上。压测跑完一堆报告,人工看很费劲,让Agent自动分析瓶颈点、生成优化建议,这个价值也很大。
6. 给准备上手的团队几点实在建议
如果你所在的团队也想尝试这套方案,我的建议是从小场景切入,别一上来就搞大而全。先选一条简单的链路(比如登录加查询),把整个流程跑通,验证效果后再逐步扩展。一开始就想着覆盖所有业务,大概率会卡在某个复杂场景上出不来。
接口文档的质量决定了生成质量的上限。如果团队的接口文档本身就是一坨,那先花时间把文档整理好,比直接上Agent更有效。Agent再智能,也变不出不存在的接口信息。
人工审核环节不能省。至少在现阶段,生成的脚本必须经过人工审核才能用于正式压测。审核的重点是参数关联、断言逻辑、以及配置参数。完全放手让Agent生成直接跑,风险太大。
把修复经验沉淀下来。每次人工修复的错误,都记录下来,定期整理进Prompt或者校验规则里。这套系统的能力是随着错误案例的积累而增长的,用得越久越顺手。
最后说个心态问题。Agent生成脚本不是要取代测试工程师,而是把工程师从重复劳动里解放出来,去做更有价值的事——比如设计更合理的压测场景、分析性能瓶颈、优化系统架构。工具永远是工具,用得好不好,还是看用工具的人。我在实际项目里最大的体会是:把Agent当成一个需要培养的新人,而不是一个即插即用的工具,给它清晰的指令、足够的上下文、以及持续的反馈,它才能真正帮上忙。