关于 vitest-best-practices
面向开发者的 Vitest 测试实践指南,涵盖测试清晰度、隔离性与性能,并按需提供分主题参考规则。
主要功能
- 01测试组织与命名指导
- 02Arrange–Act–Assert 结构
- 03使用 it.each() 编写参数化测试
- 04断言与测试替身
- 05异步、性能和快照测试指导
适用场景
- 01设计清晰的 Vitest 测试套件
- 02避免 Mock 泄漏和顺序依赖故障
- 03为外部依赖选择测试替身
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gohypergiant/ agent-skills --skill vitest-best-practices安装后在 Claude Code 中输入/vitest-best-practices确认已加载。
详细介绍
Vitest 最佳实践
编写可维护、高效 vitest 测试的全面模式。专注于测试组织、清晰度和性能方面的专家级指导。
编写 Vitest 测试时绝不要做的事
- 绝不要跳过全局 mock 清理配置 - 手动清理看似安全,却会造成"隔空作用"式失败:测试文件 A 中的 mock 泄漏到 3 个文件之后运行的测试文件 B 中,导致仅在特定测试运行顺序下才出现的非确定性失败。这些海森堡 bug 会在 CI 调试中浪费数小时。在
vitest.config.ts中一次性配置clearMocks: true、resetMocks: true、restoreMocks: true,即可消除这整类依赖顺序的失败。 - 绝不要将 describe 块嵌套超过 2 层 - 深层嵌套会造成认知负担和过度缩进。把上下文放进测试名称中:
it('should add item to empty cart')对比describe('when cart is empty', () => describe('addItem', ...))。 - 绝不要 mock 你自己的纯函数 - Mock 内部代码会让测试变得脆弱且价值降低。只 mock 外部依赖(API、数据库、第三方库)。优先选择 fake > stub > spy > mock。
- 绝不要使用
toBeTruthy()或toBeDefined()这类宽松断言 - 这些断言会为你从未预期的多个不同值通过:toBeTruthy()对1、"false"、[]和{}都通过——它们在语义上完全不同。当重构将getUser()的返回值从{id: 1}改为1时,你的测试仍然通过,但生产代码却会崩溃。宽松断言制造出在生产环境中蒸发殆尽的虚假信心。toBeTypeOf()不是宽松断言。 - 绝不要测试实现细节而非行为 - 验证"函数 X 被调用了 3 次"的测试会造成虚假失败:你通过记忆化优化代码,使其只调用 X 一次,所有测试都失败,而用户体验却完全相同(甚至更快)。这些测试实际上是在惩罚性能改进和重构。测试用户能观察到的内容(给定输入得到的输出),而不是你的代码内部如何实现。
- 绝不要在测试之间共享可变状态 - 依赖执行顺序或前一个测试状态的测试会造就脆弱、不可靠的测试套件。每个测试都必须完全独立,拥有全新的 setup。
- 绝不要在测试文件中使用
any或跳过类型检查 - 当实现签名发生变化时,带as any的测试会静默通过,同时却在用错误的参数调用函数。你会发布 TypeScript 本可以捕获的破损代码。测试是可执行的文档:user as any什么也没传达,而createTestUser(Partial<User>)则准确展示了此测试用例中哪些属性才是重要的。
编写测试前,先问
在实现测试之前,应用这些专家思维模式:
测试隔离与 setup
- 清理逻辑应该放在哪里? 分层思考:配置消除整类错误(vitest.config.ts 中的 mock 清理),setup 文件处理项目级关注点(自定义匹配器、全局 mock),beforeEach 处理测试特定的状态。每个测试各自做 mock 清理,就像每个函数各自做 null 检查——能工作,却没抓住重点。把关注点推到合适的最高层。
- 此测试是否依赖之前的测试或共享状态? 测试套件是平行宇宙——每个测试无论第一个运行、最后一个运行还是单独运行,都应表现一致。状态依赖会造就"量子测试",通过与否取决于执行顺序。如果一个测试需要另一个测试的数据,它们实际上是被人为拆成两个的一个测试。
测试什么
- 我在测试行为还是实现? 测试用户实际体验到的内容(输入 → 输出),而不是代码如何实现(调用了哪些函数)。实现测试会在安全重构时失效。
- 我能使用的最简单的依赖是什么? 真实实现 > fake > stub > spy > mock。在这个层级中每下降一步都会增加脆弱性。只有在使用真实代码不切实际时(外部 API、慢操作)才使用 mock。
测试清晰度
- 别人能在 5 秒内理解这个测试吗? 遵循 AAA 模式(Arrange、Act、Assert),并划清边界。如果 setup 很复杂,就提取为名称具有描述性的辅助函数。
- 同一行为是否存在多种变体? 使用
it.each()编写参数化测试,而不是复制测试结构。每个概念只做一个断言可让测试保持聚焦。
性能与维护
- 这个测试在 6 个月后仍然有价值吗? 避免测试框架内部机制或琐碎操作。专注于业务逻辑、边界情况和真正能防止 bug 的错误处理。
- 这个测试是否快到能在每次保存时运行? 避免在测试中进行昂贵的操作。对数据库使用 fake,对延迟使用 mock 计时器,对外部调用使用 stub。测试应在毫秒内完成。
本 Skill 涵盖的内容
关于 vitest 测试模式的专家指导:
- 组织 - 文件放置、命名、describe 块结构
- AAA 模式 - Arrange、Act、Assert,实现即时清晰
- 参数化测试 - 使用
it.each()减少重复 - 错误处理 - 测试异常、边界情况、故障注入
- 断言 - 使用严格断言捕获意外值
- 测试替身 - fake、stub、mock、spy 的层级以及各自的使用场景
- 异步测试 - Promise、async/await、计时器、并发测试
- 性能 - 通过高效 setup 和全局配置实现快速测试
- Vitest 特性 - 覆盖率、监听模式、setup 文件、配置发现
- 快照测试 - 快照何时有助于可维护性、何时有害
如何使用
本 Skill 采用 渐进式披露 结构,以尽量减少上下文占用:
1. 从概览开始(AGENTS.md)
阅读 AGENTS.md,获取所有规则的精简概览,其中包含一行摘要以及发现现有测试配置的工作流。
2. 检查现有测试配置
在编写测试之前:
- 首先检查
vitest.config.ts中的全局 mock 清理设置(clearMocks、resetMocks、restoreMocks) - 然后搜索 setup 文件(
test/setup.ts、vitest.setup.ts等)并分析其配置 - 参见 AGENTS.md 中的工作流
3. 按需加载特定规则
使用以下明确的触发条件来判断何时加载每个参考文件:
强制加载(加载整个文件):
- 使用 promise/定时器编写异步测试 → async-testing.md
- 处理 mock、stub、spy 或 fake → test-doubles.md
看到以下模式时加载:
- 嵌套 describe 块超过 2 层 → organization.md
- 测试文件未与实现放在一起 → organization.md
- 测试缺少清晰的 Arrange/Act/Assert 结构 → aaa-pattern.md
- 测试代码重复但略有差异 → parameterized-tests.md
- 缺少错误用例测试或边界情况覆盖不足 → error-handling.md
toBeTruthy()或toBeDefined()等宽松断言 → assertions.md- 测试运行缓慢(每个测试 >100ms) → performance.md
- 需要覆盖率、监听模式或 vitest 特有功能 → vitest-features.md
- 考虑或审查快照测试 → snapshot-testing.md
除非特别需要,否则不要加载:
- 如果测试很快(<50ms),不要加载 performance.md
- 除非提到快照,否则不要加载 snapshot-testing.md
- 对于基础测试编写,不要加载 vitest-features.md
4. 应用模式
每个参考文件包含:
- ❌ 展示反模式的错误示例
- ✅ 展示最佳实现的正确示例
- 对该模式为何重要的解释
快速示例
有关完整的 before/after 示例,请参阅 quick-start.md,了解此技能如何将不清晰的测试转变为清晰、可维护的测试。
评论
支持 Markdown