我让 Nuxt 中的 Vitest 套件运行速度提升十倍

发布: (2025年12月20日 GMT+8 19:21)
8 min read
原文: Dev.to

看起来您只提供了来源链接,而没有贴出需要翻译的正文内容。请把您想要翻译的文本(文章正文、代码块说明等)粘贴在这里,我会按照要求保留源链接并将其余部分翻译成简体中文。

为什么自动化测试很重要

  • 安全网: 防止代码库增长时出现回归。
  • 信心: 可以快速添加并验证新功能。
  • 速度 vs. 可靠性: 测试越多 → 运行时间越长,这可能成为痛点。

我的 Nuxt 模块用于 Neon DB 连接,最初运行时间超过 180 + seconds。这种延迟毁掉了我的开发者体验(DevEx),迫使我重新思考测试架构。

原始(慢速)设置

  1. 每个测试文件一个 Nuxt 应用 – 每个文件都会创建其自己的独立 Nuxt 实例。

  2. 顺序执行 – 测试一个接一个地运行,因此每次启动的延迟都会累计。

  3. 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 实例,该实例只启动一次。

解决思路概览

  1. 全局设置 – 在 Vitest 启动任何测试之前 一次性 挂载 Nuxt 应用。
  2. 共享测试上下文 – 将同一个 Playwright 浏览器(或虚拟浏览器)暴露给每个测试文件。
  3. 环境变量 – 告诉 @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

BeforeAfter
~180 秒(3 分钟)不,这不可能。

> “这是我必须非常明确且诚实的时刻,因为你已经碰到了硬性界限,而不是缺少技巧。”

我后来觉得这很可笑,但当时这是残酷的事实。我浪费了数小时追逐自己的影子,AI却开心地帮助并鼓励我继续。

我有快速测试,但它们难以观察:要么根本没有输出,要么输出中充斥着无关的调试信息,或者需要平台依赖的脚本命令来润色它。

现在整个套件在约20 秒内运行。

思路

如果我不再尝试 滥用 Vitest 去做它本身并不适合的事,会怎样?
如果我 把所有测试文件聚合成一个大型套件,会怎样?

这样测试应用只会 构建并挂载一次,随后所有测试都能快速运行。是的,这会是一个包含众多测试用例的大测试文件,但从运行时角度来看这并不重要。我仍然可以通过在单个 e2e.test.ts 文件中 动态导入 各个定义文件来保持源码的隔离——e2e.test.ts 是唯一被 Vitest 执行的文件。

最终方案

我删除了 vitest.config.ts 中的 globalSetupsetupFiles。现在我只需要一个简洁的小型 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')

代码:

  1. 准备 通过专用的 setup 函数启动 Nuxt 测试应用。
  2. 等待 应用就绪后,依次导入并执行每个测试套件文件。

就这么简单——没有 hack,也没有控制台杂乱。它运行得非常顺畅且依旧快速:约 20 秒即可完成。现在输出在控制台中保持干净。

要点

  • 我终于与 Vitest + Nuxt 斗争并取得了胜利。
  • 如果你想了解更多实现细节或有改进建议,请在评论中告诉我。

AI 工具:有帮助但并非万无一失

Copilot 和 ChatGPT 都像是典型的开发者,只会抛出“应该可以工作”的方案,却没有看到全局。它们失败的地方——而我最终成功的地方——在于能够后退一步重新思考整个局面。这种差距在人类开发者与人工伪智能之间仍然存在。

注意: 这并不是反 AI 的抨击。AI 每天都在帮助我,我也很享受它的帮助。我只是认为我们需要意识到它的局限性。这对我来说是另一个很好的教训,也希望你觉得有趣。

期待您的反馈和提问!

Back to Blog

相关文章

阅读更多 »