- 性能测试
- 接口测试
- CLI
【免费下载链接】artillery
The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
本指南以仓库内 examples/artillery-plugin-hello-world 示例插件为主线,完整讲解 Artillery 的插件接口(v1/v2 两种形态)、插件包的命名与加载约定、如何读取测试脚本配置、如何为所有场景挂载自定义请求钩子(beforeRequest)、如何通过事件机制上报自定义计数器,以及插件的清理钩子(cleanup)。读完本文,你将具备从零编写一个可被 Artillery 自动发现、加载并生效的 Node.js 插件的能力,并理解插件从解析到实例化的底层调用链。
一、这个示例插件展示了什么
artillery-plugin-hello-world是一个刻意保持“最小可用”的 Artillery 插件,其 README 明确列出了它要演示的四件事:
- Artillery 的插件接口长什么样;
- 一个骨架级(barebones)插件是如何构建起来的;
- 如何在插件内部读取测试脚本(test script)的属性;
- 如何为场景挂载自定义 hook,从而在压测过程中做一些“有趣”的事。
换句话说,它不依赖任何外部服务,也不实现复杂的指标采集,而是把插件开发的最小完整闭环——定义、加载、配置读取、钩子注入、事件上报、清理——一次性走通,是理解 Artillery 扩展体系的最佳入门样本。
二、插件的两种形态:v1 构造函数与 v2{ Plugin }导出
从 Artillery 的插件加载源码 packages/artillery/lib/load-plugins.ts 可以看到,插件包在加载时会按导出形状被识别为不同版本:
- v1 插件:模块直接导出一个构造函数(
typeof PluginExport === 'function'),实例化时接收script.config和事件总线; - v2 插件:模块导出一个对象,其中含
Plugin构造函数(typeof PluginExport.Plugin === 'function'),实例化时接收完整的script、事件总线与运行选项。
本示例采用 v2 形态,在 index.js 中通过一行代码完成导出:
module.exports.Plugin = ArtilleryHelloWorldPlugin;在本地运行器 packages/artillery/lib/platform/local/worker.ts 中,两种形态的实例化方式对应为:
// v1:传 script.config result.plugin = new result.PluginExport(script.config, stubEE); // v2:传整个 script result.plugin = new result.PluginExport.Plugin(script, stubEE, options);从源码结构看,v2 之所以能拿到整个script,是为了让插件有更大的自由度去修改脚本对象(例如注入处理器函数),而 v1 只拿到配置部分。本示例使用的 v2 接口正是当前主推的形态。
三、插件包命名与加载路径约定
Artillery 默认会到 Node.js 的包查找路径中寻找插件包,且要求包名带artillery-plugin-前缀——因此hello-world插件对应的包名必须是artillery-plugin-hello-world,这一点同时体现在 package.json 的name字段与加载逻辑中。
加载顺序在 load-plugins.ts 中定义得非常明确:
- 先按裸包名(bare specifier)从标准
node_modules解析(用户安装的副本优先); - 其次查找 Artillery 内置包目录;
- 最后才尝试
ARTILLERY_PLUGIN_PATH环境变量中冒号(:)分隔的各个路径。
let requirePaths = ['', BUILTIN_PACKAGES_DIR]; if (process.env.ARTILLERY_PLUGIN_PATH) { requirePaths = requirePaths.concat(process.env.ARTILLERY_PLUGIN_PATH.split(':')); }因此,当你在开发插件、尚未把包发布安装到node_modules时,用ARTILLERY_PLUGIN_PATH指向上级目录即可让 Artillery 找到它。原文档在 README 中也明确提示了这一点:ARTILLERY_PLUGIN_PATH正是为“开发中的插件”提供的额外查找位置。
除路径外,还可以通过环境变量ARTILLERY_PLUGINS(JSON 字符串)附加额外的插件配置,详见 load-plugins.ts 中的loadPluginsConfig实现。
四、剖析插件实现:构造函数里做了什么
打开 index.js,整个插件只有三部分:构造函数、一个处理器函数、一个cleanup钩子。下面逐段拆解。
4.1 保存 script 与 events
function ArtilleryHelloWorldPlugin(script, events) { this.script = script; this.events = events; // ... }构造函数接收两个参数:
script:完整的测试脚本对象(即 YAML 解析后的config+scenarios结构);events:一个 EventEmitter,插件可以订阅stats(新一批指标产生时触发)、done(所有虚拟用户完成时触发)事件,也可以用它发出自定义指标。
从 worker.ts 的调用来看,v2 插件实例化时传入的正是完整script、事件桩(stubEE)与options。注释中还特别说明:v1/v2 插件不会从每个 runner 实例直接订阅stats/done,而是在launch-platform层统一订阅聚合后的结果,避免插件收到未聚合的零散事件。
4.2 读取插件自身的配置
const pluginConfig = script.config.plugins['hello-world']; this.greeting = pluginConfig.greeting || 'hello, world';插件可以直接访问script.config.plugins['hello-world']拿到自己在测试脚本中的配置块,并给出默认值兜底。这正是原文档所说的“如何检查测试脚本属性”——配置、target、phases 等一切脚本内容对插件都是可见的,例如:
debug('target is:', script.config.target);4.3 向所有场景注入 beforeRequest 钩子
script.config.processor = script.config.processor || {}; script.config.processor.pluginHelloWorldBeforeRequestHook = (_req, _vuContext, events, next) => { console.log(this.greeting); events.emit('counter', 'greeting_count', 1); return next(); }; script.scenarios.forEach((scenario) => { scenario.beforeRequest = scenario.beforeRequest || []; scenario.beforeRequest.push('pluginHelloWorldBeforeRequestHook'); });这段代码做了三件事,正是插件“动手术”的典型手法:
- 在
script.config.processor对象中注册一个自定义函数(Artillery 的处理器机制); - 该函数的签名符合
beforeRequest钩子约定:接收请求对象_req、虚拟用户上下文_vuContext、events事件总线以及next回调;调用next()表示钩子结束、继续后续流程; - 遍历
script.scenarios,把钩子名追加到每个场景的beforeRequest列表末尾——由此实现对所有场景生效,无需用户在 YAML 里逐个声明。
HTTP 引擎在 packages/artillery/lib/core/engine_http.ts 中会在每次请求发出前依次执行这些beforeRequest处理器(包括场景级与请求级),这也是“在请求前打印问候语”之所以能落地的底层机制。
4.4 上报自定义计数器
钩子内部通过events.emit('counter', 'greeting_count', 1)上报一个名为greeting_count的自定义计数器。控制台报告器 packages/artillery/lib/console-reporter.ts 会读取report.counters并按名称排序打印,最终在测试报告尾部以greeting_count: N的形式呈现,无需任何额外配置即可看到插件产生的新指标。
4.5 清理钩子 cleanup
ArtilleryHelloWorldPlugin.prototype.cleanup = (done) => { debug('cleaning up'); done(null); };Artillery 在退出前会调用每个插件的cleanup,给插件一次冲刷在途数据、写盘等收尾机会。在 packages/artillery/lib/launch-platform.ts 与 packages/artillery/lib/platform/local/worker.ts 中都有对plugin.cleanup的调用,且在done回调中传入错误对象(null表示无错)。本示例只打印一条调试日志,实际插件常在此处刷新缓存、关闭连接或写报告文件。
五、配套的测试脚本与运行方式
示例自带的 test.yml 是验证插件的完整测试脚本:
config: target: "http://asciiart.artillery.io:8080" phases: - arrivalRate: 1 duration: 10 plugins: hello-world: greeting: "Hello world! 👋" scenarios: - flow: - get: url: "/"要点解读:
config.plugins.hello-world.greeting是插件的配置块,会被插件构造函数读取并覆盖默认问候语;phases以每秒 1 个新虚拟用户的速率持续 10 秒;- 场景只有一个 HTTP
GET /请求,用于触发beforeRequest钩子。
在示例目录下运行(注意:需先安装 Artillery 本体,示例插件作为本地包由ARTILLERY_PLUGIN_PATH指向):
ARTILLERY_PLUGIN_PATH=`pwd`/.. DEBUG=plugin:hello-world artillery run test.yml命令说明:
ARTILLERY_PLUGIN_PATH=pwd/..:把插件包所在目录(示例的上级目录,即examples/,其中包含名为artillery-plugin-hello-world的包目录)加入插件查找路径;DEBUG=plugin:hello-world:开启debug库中该插件命名空间的调试输出,对应 index.js 中的require('debug')('plugin:hello-world');artillery run test.yml:以本地模式运行测试。
运行后,终端会依次出现插件初始化时的调试信息(如target is:与cleaning up)、每条请求前的问候语打印,以及报告尾部由插件上报的greeting_count计数器(见上文两张截图)。loadPlugins的单元测试 packages/artillery/test/unit/load-plugins.test.js 与 CLI 测试 packages/artillery/test/cli/custom-plugin.test.js 均演示了通过ARTILLERY_PLUGIN_PATH加载本地插件这一方式的自动化验证。
六、插件的加载与初始化调用链
把上面的零散证据串起来,一个插件从被识别到生效的完整链路是:
- 解析插件规格:
loadPluginsConfig合并脚本内的config.plugins与环境变量ARTILLERY_PLUGINS(load-plugins.ts); - 按序查找包:依次在裸包路径、内置包目录、
ARTILLERY_PLUGIN_PATH中require.resolve,再通过import()加载(兼容 CJS 与 ESM,见 load-plugins.ts); - 识别版本:按导出形状判定 v1/v2(load-plugins.ts);
- 实例化:在本地 worker 中按版本调用构造函数(worker.ts),在平台层(如 Fargate/Lambda 场景)则先给一个深拷贝的
dummyScript做事件订阅注册,避免在分发阶段就修改真实脚本对象(launch-platform.ts); - 钩子生效:插件修改
script.config.processor与各scenario.beforeRequest,HTTP 引擎在每次请求前执行这些处理器(engine_http.ts); - 指标呈现:插件通过
events.emit('counter', ...)上报的计数器由控制台报告器汇总打印(console-reporter.ts); - 退出清理:
cleanup被依次调用(launch-platform.ts)。
值得一提的是,平台层在初始化时给 v1/v2 插件传递的是深拷贝的 dummyScript,源码注释明确指出:如果让插件直接操作真实脚本对象并在其中附加处理器,会导致派发 worker 时失败(launch-platform.ts)——这是插件开发中容易踩坑、但示例这种“单机运行”场景不会触发的细节。
七、在此基础上还能做什么
原文档在 README 的 “Learn more” 一节推荐了更多成熟插件作为参考,这些参考实现大多已在当前仓库中开源,可以直接对照阅读:
- packages/artillery-plugin-publish-metrics:向 Datadog、Prometheus、CloudWatch、New Relic、Splunk、Mixpanel 等后端发布指标,展示了订阅
stats事件做指标转发的能力; - packages/artillery-plugin-expect:在测试中对响应做断言,展示了在请求层面深度拦截与校验的模式;
- packages/artillery-plugin-apdex:基于响应时间计算 Apdex 评分,展示了利用聚合指标派生新指标的做法;
- packages/artillery-plugin-metrics-by-endpoint:按端点拆分指标,展示了结合 URL 模板场景的处理方式;
- packages/artillery-plugin-fake-data:生成随机测试数据,展示了在脚本中注入数据生成能力;
- packages/artillery-plugin-slack:测试结束后推送通知,展示了订阅
done事件的典型用法。
此外,Artillery 的官方扩展 API 文档与《创建自定义插件》博客是深入了解插件机制的权威补充资料(原文已给出链接,此处不再罗列外部地址)。核心要点可以归纳为:插件接口 + Node.js 生态(“任何需求几乎都有对应的 npm 包”)是 Artillery 扩展能力的双引擎——你的插件完全可以调用任意 Node.js 模块来完成指标入库、消息通知、数据加工等任何定制逻辑。
八、小结
通过artillery-plugin-hello-world这个最小示例,我们完整走通了 Artillery 插件开发的全部关键环节:
| 能力 | 示例中的落点 | 源码依据 |
|---|---|---|
| v2 插件导出 | module.exports.Plugin | index.js、worker.ts |
| 读取脚本配置 | script.config.plugins['hello-world'] | index.js |
| 注入全局钩子 | 改写scenario.beforeRequest | index.js、engine_http.ts |
| 自定义指标 | events.emit('counter', ...) | index.js、console-reporter.ts |
| 退出清理 | prototype.cleanup | index.js、launch-platform.ts |
| 开发期加载 | ARTILLERY_PLUGIN_PATH | load-plugins.ts |
对任何希望扩展 Artillery 的团队或个人而言,这个示例提供了可复制的最小骨架:定义{ Plugin }导出 → 读取配置 → 注入处理器 → 上报指标 → 实现清理。后续只需按需求替换业务逻辑,就能把它升级为功能完备的生产级插件。
- 性能测试
- 接口测试
- CLI
【免费下载链接】artillery
The complete load testing platform. Everything you need for production-grade load tests. Serverless & distributed. Load test with Playwright. Load test HTTP APIs, GraphQL, WebSocket, and more. Use any Node.js module.
相关推荐
深入解析 Artillery 自定义引擎开发:以 artillery-engine-example 为例掌握引擎 API 与实战接入
深入解析 Artillery 自定义引擎开发:以 artillery engine example 为例掌握引擎 API 与实战接入 导读 本指南以仓库内 ar
性能测试接口测试CLIEclipse Theia 无头插件(Headless Plugin)与自定义插件 API 实战:以 plugin-gotd 示例插件为例
Eclipse Theia 无头插件(Headless Plugin)与自定义插件 API 实战:以 plugin gotd 示例插件为例 本篇技术指南围绕 E
IDE代码编辑器开发工具前端桌面应用插件系统后端AI 应用Kubebuilder 插件扩展实战:从自定义 Plugin 接口到构建专属 CLI
Kubebuilder 插件扩展实战:从自定义 Plugin 接口到构建专属 CLI Kubebuilder 不仅是开箱即用的 CRD 脚手架工具,更提供了一套
开发者工具代码生成CLI云原生后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考