我让 Nuxt 中的 Vitest 套件运行速度提升十倍
看起来您只提供了来源链接,而没有贴出需要翻译的正文内容。请把您想要翻译的文本(文章正文、代码块说明等)粘贴在这里,我会按照要求保留源链接并将其余部分翻译成简体中文。
为什么自动化测试很重要
- 安全网: 防止代码库增长时出现回归。
- 信心: 可以快速添加并验证新功能。
- 速度 vs. 可靠性: 测试越多 → 运行时间越长,这可能成为痛点。
我的 Nuxt 模块用于 Neon DB 连接,最初运行时间超过 180 + seconds。这种延迟毁掉了我的开发者体验(DevEx),迫使我重新思考测试架构。
原始(慢速)设置
-
每个测试文件一个 Nuxt 应用 – 每个文件都会创建其自己的独立 Nuxt 实例。
-
顺序执行 – 测试一个接一个地运行,因此每次启动的延迟都会累计。
-
E2E 辅助函数 – 每个文件都会调用
await setup({ rootDir: fileURLToPath(new URL('./neon-test-app', import.meta.url)), })这会每次都实例化一个全新的 Nuxt 应用。
结果?在得到任何反馈之前,需要等待约 3 分钟。
第一次改进 – 合并测试应用
我将多个独立的测试应用合并为一个 单一的 Nuxt 应用:
- 每个测试仍然拥有自己的页面(不同的 URL),通过按钮执行 SQL 操作。
- 我把多个
app/pages目录合并为一个,并对app.vue进行了一些调整。
结果: 大约提升了 2 倍的速度,因为复用了同一个应用,但整个套件仍然远未达到最佳状态。
实际解决方案 – 在所有测试中共享单个 Nuxt 实例
目标
当任何测试执行:
const page = await createPage()
时,它应该 复用同一个已经启动的 Nuxt 实例,该实例只启动一次。
解决思路概览
- 全局设置 – 在 Vitest 启动任何测试之前 一次性 挂载 Nuxt 应用。
- 共享测试上下文 – 将同一个 Playwright 浏览器(或虚拟浏览器)暴露给每个测试文件。
- 环境变量 – 告诉
@nuxt/test-utils使用哪个应用进行挂载。
1️⃣ 添加全局设置文件
vitest.config.ts
export default defineConfig({
// …other config
globalSetup: ['./node_modules/@nuxt/test-utils/dist/runtime/global-setup.mjs'],
})
该文件会在任何 Vitest worker 启动之前 仅运行一次,并在模拟浏览器中挂载 Nuxt 应用。
2️⃣ 配置要挂载的应用
创建(或编辑)一个小脚本来设置 NUXT_TEST_OPTIONS:
// vitest.global-setup.ts (or any file you import via globalSetup)
import { resolve, fileURLToPath, URL } from 'node:path'
const rootDir = resolve(
fileURLToPath(new URL('.', import.meta.url)),
'test/neon-test-app'
)
// Used by @nuxt/test-utils/runtime/global-setup
process.env.NUXT_TEST_OPTIONS = JSON.stringify({
// Path to the test app
rootDir,
// Do NOT create a Playwright browser here – we’ll do it lazily
browser: false,
})
说明:@nuxt/test-utils 会在全局‑setup 阶段读取 NUXT_TEST_OPTIONS,并挂载指定的应用。
3️⃣ 设置共享的浏览器实例
e2e.setup.ts
import { useTestContext, createBrowser } from '@nuxt/test-utils/e2e'
beforeAll(async () => {
const ctx = useTestContext()
// Initialise the virtual browser only once
if (!ctx.browser) {
await createBrowser()
}
})
useTestContext()用来获取跨文件共享的 全局测试上下文。createBrowser()会 一次性 启动 Playwright(或 Puppeteer)浏览器;后续测试会复用它。
4️⃣ 像往常一样编写测试
import { createPage } from '@nuxt/test-utils/e2e'
test('my feature works', async () => {
const page = await createPage()
await page.goto('/my-test-page')
// …assertions
})
所有测试共享同一个已挂载的 Nuxt 应用以及同一个浏览器实例,从而消除每个文件单独启动的开销。
Result
| Before | After |
|---|---|
| ~180 秒(3 分钟) | 不,这不可能。 > “这是我必须非常明确且诚实的时刻,因为你已经碰到了硬性界限,而不是缺少技巧。” 我后来觉得这很可笑,但当时这是残酷的事实。我浪费了数小时追逐自己的影子,AI却开心地帮助并鼓励我继续。 我有快速测试,但它们难以观察:要么根本没有输出,要么输出中充斥着无关的调试信息,或者需要平台依赖的脚本命令来润色它。 |
现在整个套件在约20 秒内运行。
思路
如果我不再尝试 滥用 Vitest 去做它本身并不适合的事,会怎样?
如果我 把所有测试文件聚合成一个大型套件,会怎样?
这样测试应用只会 构建并挂载一次,随后所有测试都能快速运行。是的,这会是一个包含众多测试用例的大测试文件,但从运行时角度来看这并不重要。我仍然可以通过在单个 e2e.test.ts 文件中 动态导入 各个定义文件来保持源码的隔离——e2e.test.ts 是唯一被 Vitest 执行的文件。
最终方案
我删除了 vitest.config.ts 中的 globalSetup 和 setupFiles。现在我只需要一个简洁的小型 e2e.test.ts 文件:
import { fileURLToPath } from 'node:url'
import { setup } from '@nuxt/test-utils/e2e'
// only setup nuxt-test-app ONCE
await setup({
rootDir: fileURLToPath(new URL('../neon-test-app', import.meta.url)),
// Playwright browser is not required for now
browser: false,
})
// import and run E2E test suites AFTER the test app is ready
await import('../neon-test-suites/01-basic')
await import('../neon-test-suites/02-select')
await import('../neon-test-suites/03-insert')
await import('../neon-test-suites/04-update')
await import('../neon-test-suites/05-delete')
代码:
- 准备 通过专用的
setup函数启动 Nuxt 测试应用。 - 等待 应用就绪后,依次导入并执行每个测试套件文件。
就这么简单——没有 hack,也没有控制台杂乱。它运行得非常顺畅且依旧快速:约 20 秒即可完成。现在输出在控制台中保持干净。
要点
- 我终于与 Vitest + Nuxt 斗争并取得了胜利。
- 如果你想了解更多实现细节或有改进建议,请在评论中告诉我。
AI 工具:有帮助但并非万无一失
Copilot 和 ChatGPT 都像是典型的开发者,只会抛出“应该可以工作”的方案,却没有看到全局。它们失败的地方——而我最终成功的地方——在于能够后退一步并重新思考整个局面。这种差距在人类开发者与人工伪智能之间仍然存在。
注意: 这并不是反 AI 的抨击。AI 每天都在帮助我,我也很享受它的帮助。我只是认为我们需要意识到它的局限性。这对我来说是另一个很好的教训,也希望你觉得有趣。