--- url: /guide/learn/writing-tests-with-ai.md --- # 使用 AI 编写测试 AI 编码助手可以帮助你更快地编写测试,但输出质量在很大程度上取决于你提供的上下文。模糊的提示会产生模糊的测试,而带有正确上下文的明确提示则能生成真正值得保留的测试。 本页面介绍了如何从 AI 工具中获得高质量的测试代码,以及在审查结果时需要注意的问题。 ## 提供上下文 你能做的最重要的一件事就是给 AI 足够的上下文,让它理解正在测试什么。 从源文件本身开始。AI 需要看到实际的实现,而不仅仅是功能的描述。包含完整的文件,或者至少包含你想要测试的函数及其导入和类型。 分享同一项目的现有测试文件。这有助于 AI 匹配你的约定:你是使用 `test` 还是 `it`,如何组织 `describe` 块,是否更喜欢 `test.extend` 夹具还是 `beforeEach`,以及如何命名测试。AI 工具擅长模式匹配,但它们需要有可以匹配的模式。 包含你的 Vitest 配置,特别是如果你启用了 [`globals`](/config/globals)、设置了自定义 [`environment`](/config/environment) 或配置了 [`setupFiles`](/config/setupfiles)。没有这些上下文,AI 可能会生成不必要的导入,使用错误的测试环境,或者遗漏测试依赖的设置。 如果被测代码有依赖项需要 Mock,也请分享这些文件(或至少提供它们的类型签名)。AI 无法为它从未见过的数据库客户端编写有用的 Mock。 ::: tip 如果你的项目中有一个包含编码约定的 `AGENTS.md` 或类似文件,也请一并包含。许多 AI 工具会自动识别这些文件并遵循其中定义的规则。 ::: ## 编写良好的提示 明确的提示比通用的提示能产生更好的测试。让我们比较一下这两个示例: **模糊的:** “为 `userService.js` 编写测试” 这会生成测试,但它们可能很浅显:每个函数只有一个快乐路径测试,最小程度的边缘情况覆盖,并且测试名称通用。 **更好的:** “为 `userService.js` 中的 `createUser` 函数编写测试。覆盖验证错误(缺少名称、无效的电子邮件格式、重复的电子邮件)、成功创建路径,并验证密码在存储前已进行哈希处理。” 这告诉 AI 重点关注哪个函数,哪些场景是重要的,以及需要验证哪些行为。输出会更全面且更相关。 ### 编写更好提示的技巧 * 明确提出边界情况。“包含空输入、边界值和错误处理的测试”比让 AI 自行判断能得到更全面的覆盖。如果没有这样的提示,大多数工具只会生成少量成功路径测试,然后就停下来。 * 如果希望使用特定的 Vitest 功能,请明确提及。“对错误消息使用 `toMatchInlineSnapshot`”或“对不同货币格式使用 `test.for`”可以引导 AI 使用合适的工具,而不是退回到重复的复制粘贴式测试。 * 如果测试异步代码,请明确说明。“该函数返回 Promise”或“这里会调用外部 API”有助于 AI 使用 `async`/`await` 以及 `.resolves` 和 `.rejects` 等合适的匹配器。 * 告诉 AI 不要做什么。“基于真实实现进行测试,不要模拟任何模块”或“不要使用快照测试”可以避免不希望出现的常见默认做法。AI 工具往往会过度模拟,明确的约束可以避免这一点。 * 描述你想要的测试结构。“按方法使用 `describe` 块对测试分组”或“使用 `test.extend` fixture 处理数据库连接,而不是使用 `beforeEach`”可以省去事后重构输出的工作。 * 请求添加测试时引用现有测试。“遵循 `auth.test.js` 中测试的相同风格”比从头描述风格更有效。AI 会从示例中学习命名约定、断言模式和导入风格。 * 如果第一版结果不理想,就继续迭代。“这些测试过于关注实现细节。请重写它们,只断言返回值和抛出的错误”是很有效的后续请求。通过对话逐步完善,通常比一开始就试图写出完美提示更有效。 ## 审查 AI 生成的测试 AI 生成的测试第一眼看起来很具有说服力,但仍可能存在问题。在提交之前,请检查以下几点。 ### 测试是否真的在断言有意义的内容? 注意那些只调用函数但只检查是否不抛出异常的测试,或者只针对 Mock 本身进行断言的测试,而不是针对行为进行断言。像这样的测试会给你虚假的信心: ```js test('创建一个用户', () => { const user = createUser('Alice', 'alice@example.com') expect(user).toBeDefined() // 这对几乎任何内容都会通过 }) ``` 更好的断言是检查实际的属性: ```js test('创建具有正确字段的用户', () => { const user = createUser('Alice', 'alice@example.com') expect(user).toMatchObject({ name: 'Alice', email: 'alice@example.com', }) expect(user.id).toBeTypeOf('string') }) ``` ### 是在测试行为还是实现? AI 倾向于过度 Mock。如果你看到一个测试 Mock 了每个依赖项,然后断言内部方法是否以特定顺序被调用,这就是在测试实现细节。这些测试每次重构时都会断裂,即使行为保持不变。 问问自己:如果有人改变了内部实现但函数仍然返回正确的结果,这个测试会断裂吗?如果会,那它可能与实现耦合过紧。更多关于这种区别的内容,请参见[实际测试](/guide/learn/testing-in-practice#what-to-test)。 ### 测试是否真的运行? 始终在提交前运行测试。AI 生成的测试可能存在导入错误、引用不存在的函数,或者使用 API 不正确。看起来正确的测试在聊天窗口中可能没问题,但实际执行时可能会立即失败: ```bash vitest run src/userService.test.js ``` ### 是否涵盖了真实的边缘情况? AI 工具倾向于生成快乐路径测试并跳过困难的情况。审查生成的测试后,问问自己:如果输入为空怎么办?如果是 `null` 或 `undefined` 呢?如果网络请求失败怎么办?如果列表为空怎么办? 如果这些场景没有被覆盖,请让 AI 添加它们,或者自己编写。 ## 迭代输出 将 AI 生成的测试视为初稿,而不是成品。良好的工作流程如下: 1. **生成**具有明确提示和良好上下文的初始测试 2. **运行**它们以立即捕获错误 3. **审查**每个测试是否存在上述问题 4. **请求修订**,如果整个部分需要改进(“这些测试 Mock 过多,请重写以测试与数据库模块的实际集成”) 5. **手动编辑**进行小修,而不是对每个细节重新提示 随着时间推移,当 AI 看到更多代码库和测试模式时,它的输出会有所改进。早期测试会为后续所有测试设定模式,因此值得花时间做好这些。 ## 常见陷阱 ### 错误的 API AI 生成的 Vitest 测试中最常见的问题是使用了错误的 API 表面。AI 模型在大量 Jest 代码上进行了训练,因此有时会生成 `jest.fn()` 而不是 `vi.fn()`,或者使用 `jest.mock` 而不是 `vi.mock`。这些会立即失败。 相关的问题是导入:如果你的配置启用了 `globals: true`,AI 可能会仍然添加 `import { test, expect } from 'vitest'`(虽然无害但没必要),或者反过来,在没有启用全局变量时生成没有导入的测试。如果你不断看到 Jest API,请将 [Vitest API 参考](/api/vi) 告知 AI,或将其包含在上下文中。 ### Mock 清理 AI 生成的测试经常设置 Spy(使用 `vi.spyOn`)或替换模块(使用 `vi.mock`),但从未恢复它们。如果你的配置没有启用 [`restoreMocks: true`](/config/restoremocks),这些 Mock 会在测试之间泄漏并导致令人困惑的失败。最简单的修复方法是全局启用该配置选项。 相关值得注意的是,AI 工具倾向于使用字符串路径(`vi.mock('./module.js')`)来 Mock 模块,而更推荐使用 `import()` 形式(`vi.mock(import('./module.js'))`),因为它在类型安全和自动重构方面更可靠。更多原因请参见[Mock 函数](/guide/learn/mock-functions#mocking-modules)。 ### 冗长的测试名称 AI 倾向于生成类似“当给定一个有效的正数和一个支持的货币代码时,正确返回格式化价格字符串”的测试名称。这些名称在拥有几十个测试时难以浏览。更短的名称能更好地描述行为,例如“格式化 USD 价格”、“对负数抛出异常”、“当没有匹配项时返回空数组”。 ### 监听模式 Vitest 默认以监听模式运行,等待文件变化并交互式重新运行测试。Vitest 会尝试检测 CI 和非交互式或代理环境并自动禁用监听模式,但这种检测可能不稳定。 当告诉 AI 代理运行测试时,请始终使用 `vitest run` 或 `vitest --no-watch` 来确保进程在测试完成后退出。