如何为 @pydantic/monty 编写 TypeScript 测试:vitest 与 WASM 测试矩阵实战指南
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
monty是一个用 Rust 编写、专为 AI 场景打造的极简安全 Python 解释器,其 npm 包@pydantic/monty让你在 Node.js 中通过崩溃隔离的子进程 Worker、在浏览器中通过 Web Worker + WASM 安全地运行不可信 Python 代码。本文带你完整拆解crates/monty-js的 TypeScript 测试体系:如何用一套 vitest 配置矩阵,同时覆盖「Node 原生绑定、WASM 运行时、真实浏览器」三条执行路径。🧪
一、先看懂测试矩阵:三套 vitest 配置
@pydantic/monty的产物有三个入口,测试自然也要按入口分工。整个测试矩阵由三份 vitest 配置文件驱动:
| 配置文件 | 覆盖范围 | 运行命令 | 前置条件 |
|---|---|---|---|
| vitest.config.ts | __test__/*.spec.ts(排除wasm_*.spec.ts) | npm test | 已构建 NAPI 原生绑定 |
| vitest.wasm.config.ts | __test__/wasm_*.spec.ts | npm run test:wasm | 先执行npm run build:wasm |
| vitest.browser.config.ts | __test__/*.spec.ts(排除node_*.spec.ts) | npm run test:browser | 先构建 wasm,并安装 Playwright Chromium |
👉 三套配置的关键区别只有一组include/exclude:默认套件刻意排除wasm_*用例(因为它需要预构建的monty_wasm_runtime.wasm),WASM 套件只跑wasm_*用例,浏览器套件则反向排除node_*用例(这些用例会读源码文件、执行 shell 命令,无文件系统的浏览器里跑不了)。
💡 三套配置都设置了
fileParallelism: false和 120 秒超时——因为每个测试都在真实的隔离 Worker 子进程中执行 Python,宁可串行慢一点,也不要并行抢资源。
二、写第一个 spec:池化夹具 + 精简断言
所有 spec 文件都在 crates/monty-js/test/ 目录,共 23 个文件(如 basic.spec.ts、async.spec.ts、pool.spec.ts 等)。它们共享两个「地基」文件:
1️⃣ 池化夹具 helpers.ts
核心思想:每个 spec 文件只创建一个共享 Worker 池,并在beforeAll/afterAll中创建与关闭。setupPool()返回一个run辅助函数——在全新会话中执行一段 Python 代码并返回结果:
const { run, pool } = setupPool() test('simple expression', async () => { t.is(await run('1 + 2'), 3) })需要直接管理会话(测试会话隔离、状态持久化等)时,用pool()拿到池子自行checkout()/close(),模式参考 basic.spec.ts 中的会话行为用例。
2️⃣ 精简断言 assertions.ts
没有直接使用expect,而是包了一层极简的t(is / deepEqual / throws / throwsAsync …),并额外提供:
throws/throwsAsync:同时断言错误类型(instanceOf: MontySyntaxError)与消息,是测试解释器异常路径的主力工具;assertMemoryError:校验MemoryError报错中的字节数,且容忍 1KB 误差——因为分配器基线在不同操作系统上会漂移几十个字节,精确匹配会让测试变成「平台专属」,这是跨平台测试很实用的一招。🎯
三、WASM 测试怎么写:先构建,再导入正确入口
WASM 套件(wasm_memory_limit、wasm_type_check、wasm_word_size等 spec)有两个必须记住的约定:
- 必须先行构建 wasm 产物。
package.json中build:wasm脚本会用wasm32-wasip1目标编译 monty-wasm-runtime,仓库顶层的 Makefile 提供了make test-wasm一键完成「构建 + 测试」:
make test-wasm- 从
/wasm入口导入 API,而不是包主入口。以 wasm_memory_limit.spec.ts 为例,它特意写import { Monty, MontyCrashedError } from '@pydantic/monty/wasm'——因为从主入口导入会拉进 NAPI 原生加载器,而 WASM 套件的环境里根本没有构建原生模块。
这套用例还示范了「崩溃语义」的测试写法:软性超限抛出可捕获的MontyRuntimeError(实例存活);硬性超限则 wasm 模块直接 trap,表现为MontyCrashedError——wasm 模块没有退出码,无法分类为 MemoryError,这正是与 Node 原生 Worker 路径的行为差异点,值得单独成用例。
四、浏览器测试:vitest browser + Playwright
vitest.browser.config.ts 展示了在无 Node 环境的浏览器中跑同一批用例的三件事:
- 真实浏览器:启用
@vitest/browser,provider 为playwright,headless Chromium 单实例; - 打桩
node:内置模块:自定义 Vite 插件把所有node:开头的导入解析到 node-builtins-stub.ts(一个会抛错的桩),别名把@pydantic/monty/node指向 node-stubs.ts; - 排除 Node 专属用例:
exclude: ['__test__/node_*.spec.ts']。
对应命令(Makefile 中会自动先装 Chromium):
make test-browser⚠️ 一个容易踩的坑:浏览器模式下 Worker 数量受限,setupPool()内部会自动为 browser 环境加maxCheckoutsPerWorker: 1,见 helpers.ts。
五、跨环境通用技巧
- 环境探测 + 条件跳过:env.ts 用
typeof window判断运行环境,导出skipIfBrowser/skipIfNode。同一 spec 文件在 Node 套件和浏览器套件中都会执行,用它们让用例「各走各的路」,而不是拆成两份文件; - 会话一律
close()归还:参考 basic.spec.ts 中try / finally的结构,避免会话泄漏影响后续用例; - 公开 API 契约测试:public_api.spec.ts 与 node_protocol_version.spec.ts 直接读取源码文件、断言导出面,保证包入口(
././node/./wasm,见 package.json 的exports字段)不被意外破坏。
六、推荐运行顺序清单 ✅
| 步骤 | 命令 | 目的 |
|---|---|---|
| 1 | make install-js | 安装 JS 依赖 |
| 2 | npm test(在crates/monty-js) | Node 原生路径全量回归 |
| 3 | make test-wasm | WASM Worker 路径(Node 驱动,无需浏览器) |
| 4 | make test-browser | 真实 Chromium 中的 WASM 路径 |
三条命令跑绿,才算一次完整的跨平台回归。
延伸阅读
- 包使用与 API 文档:crates/monty-js/README.md
- 测试支撑代码:crates/monty-js/test-support/
- 浏览器 Worker 运行时源码:crates/monty-js/ts/worker/
- WASM 运行时 crate:crates/monty-wasm-runtime/
总结一句话:一份用例、三套配置、两个共享地基(池化夹具 + 精简断言),就是@pydantic/monty用 vitest 同时守护 Node / WASM / 浏览器三条路径的完整秘诀。🚀
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考