关于 accelint-ts-testing
为编写易维护的 Vitest 测试提供详细指导,涵盖组织结构、隔离、断言、测试替身、异步场景和基于属性的测试。
主要功能
- 01清晰组织测试结构
- 02确保测试隔离且独立
- 03使用精确断言
- 04审慎选择测试替身
- 05覆盖异步与属性测试
适用场景
- 01设计新的 Vitest 测试套件
- 02检查脆弱的测试断言
- 03改进测试隔离与配置
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gohypergiant/ agent-skills --skill accelint-ts-testing安装后在 Claude Code 中输入/accelint-ts-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdREADME.mdassets/output-report-template.mdreferences/aaa-pattern.mdassertions.mdasync-testing.mderror-handling.mdorganization.mdparameterized-tests.mdperformance.mdproperty-based-testing.mdquick-start.mdsnapshot-testing.mdtest-doubles.mdvitest-features.md详细介绍
Vitest 最佳实践
编写易于维护且有效的 Vitest 测试的全面模式。重点介绍测试组织、清晰度和性能方面的专家级指导。
编写 Vitest 测试时绝对不要做的事
- 绝不要为没有行为的文件编写测试 - 常量文件(只是
export const X = value)、类型定义文件、GLSL uniform 声明和纯数据文件不包含可测试的逻辑。测试expect(MY_CONSTANT).toBe(42)什么也验证不了:如果值发生变化,测试也会随之改变,提供零保护。这些"测试"浪费 CI 时间,并在值变化时造成维护负担。测试行为(函数、逻辑、转换),而不是数据声明。如果文件只导出类型、常量或数据结构而没有函数或逻辑,则完全跳过对其的测试。 - 绝不要跳过全局 mock 清理配置 - 手动清理看似安全,但会造成"远距离作用"的失败:测试文件 A 中的 mock 泄漏到 3 个文件之后运行的测试文件 B 中,导致只有在特定顺序运行测试时才出现的非确定性失败。这些海森堡 bug 会在 CI 调试中浪费数小时。在
vitest.config.ts中一次性配置clearMocks: true、mockReset: true、restoreMocks: true,即可消除这一整类顺序相关的失败。 - 绝不要让 describe 块嵌套超过 2 层 - 深层嵌套会造成认知负担和过多的缩进。把上下文放在测试名称中:
it('should add item to empty cart')对比describe('when cart is empty', () => describe('addItem', ...))。 - 绝不要编写读起来不像句子的测试描述 - 测试描述必须以小写形式补全"it ..."这句话。应写
it('should add item to cart'),而不是it('Add item to cart')或it('It should add item to cart')。当加上"it"前缀时,描述读起来像一句话:"it should add item to cart"。首字母大写、像it('addToCart test')这样的非句子格式,或冗余的"It should",都会破坏可读性和测试输出的一致性。基于示例的测试使用it('should...')格式,而基于属性的测试使用it('property: ...')格式。 - 绝不要测试库自身已经测试过的库内部实现 - 测试
expect(array.map(fn)).toEqual(expected)是在浪费时间验证 Array.prototype.map 是否正确工作。JavaScript/TypeScript 标准库和成熟的第三方库已经被充分测试。将测试聚焦于你的业务逻辑,而不是证明 lodash、React 或语言本身能正常工作。如果你发现自己在测试"这个库函数是否做到了它声称的事?",那你测试的是错误的层面。测试你的代码如何使用库,而不是库是否能工作。 - 绝不要仅仅为了测试而导出内部函数 - 测试应通过公共 API 验证行为,而不是深入实现细节。仅仅为了便于测试而导出私有辅助函数、内部工具或实现函数,是一种代码异味,它表明:(1) 公共 API 不足以测试该行为,或 (2) 测试验证的是实现细节而非行为。如果内部逻辑复杂到需要专门测试,就把它提取到一个独立的模块中,拥有自己的公共 API 和测试文件。私有函数通过调用它们的公共函数被间接测试。
- 绝不要 mock 你自己的纯函数 - Mock 内部代码会让测试变得脆弱且价值降低。只 mock 外部依赖(API、数据库、第三方库)。优先使用 fake > stub > spy > mock。
- 绝不要使用像
toBeTruthy()或toBeDefined()这样的宽松断言 - 这些断言会为你从未打算的多个不同值通过:toBeTruthy()对1、"false"、[]和{}都通过——它们在语义上都不同。当重构把getUser()从返回{id: 1}改为返回1时,你的测试仍然通过,但你的生产代码却出错了。宽松断言制造出的虚假信心会在生产环境中蒸发。toBeTypeOf()不是宽松断言。 - 绝不要测试实现细节而不是行为 - 验证"函数 X 被调用了 3 次"的测试会造成虚假的失败:你通过记忆化优化代码使其只调用 X 一次,所有测试都失败,然而用户体验完全相同(而且更快)。这些测试实际上在惩罚性能改进和重构。测试用户观察到的东西(给定输入对应的输出),而不是你的代码内部如何实现。
- 绝不要在测试之间共享可变状态 - 依赖执行顺序或先前测试状态的测试会造成不稳定、不可靠的测试套件。每个测试都必须完全独立,拥有全新的设置。
- 绝不要在测试文件中使用
any或跳过类型检查 - 当实现签名发生变化时,使用as any的测试会静默通过,同时却用错误的参数调用函数。你会发布 TypeScript 本可以捕获的破损代码。测试是可执行的文档:user as any什么也传达不了,而createTestUser(Partial<User>)则准确说明了对这个测试用例而言哪些属性重要。 - 绝不要在未运行 TypeScript 类型检查的情况下将测试文件标记为完成 - 测试文件通常被排除在
tsconfig.json的编译路径之外,因此在项目根目录运行tsc不会捕获测试中的类型错误。测试中的类型错误会导致运行时失败、不正确的测试行为,以及来自未测试其所声称内容的测试的虚假信心。在将任何测试文件标记为"完成"之前,你必须使用项目的包管理器(npm/pnpm/bun/yarn)直接对该测试文件运行tsc --noEmit。对于 monorepo,先cd进入具体的包目录,然后再运行类型检查。在继续之前修复所有类型错误——绝不要使用as any或@ts-ignore来绕过错误。 - 绝不要假设 TypeScript 类型能防止运行时错误 - TS 类型仅在编译时存在,在运行时消失。只测试"类型有效"的输入会制造虚假的安全感。在生产环境中,函数会从未经校验的 JSON API、
JSON.parse()结果、外部库、用户输入和数据库记录接收无效数据。一个类型标注为process(data: ValidData)的函数在运行时仍可能接收到null、undefined或格式错误的对象。测试防御性编程场景:向非空参数传递null,向必填字段传递undefined,向有类型的参数传递格式错误的对象。这些"类型无效"的测试能捕获 TypeScript 无法防止的真实 bug。 - 绝不要在存在更强属性时编写弱属性 - 仅验证"未抛出异常"或"返回了一个值"的基于属性的测试提供的覆盖极少。测试 encode/decode 对时,验证往返相等性(
decode(encode(x)) === x),而不仅仅是 decode 成功。测试归一化时,验证幂等性(normalize(normalize(x)) === normalize(x)),而不仅仅是它返回一个字符串。弱属性给出虚假信心:它们通过,但并未真正验证正确性。
编写测试前,先思考
在实现测试前,应用以下专家思维模式:
是否应该测试此文件?
- 此文件是否包含需要测试的行为? 只声明常量、类型或数据结构而不包含逻辑的文件不需要测试。常量文件(
export const X = 42)、类型定义文件(type User = {...})、GLSL uniform 声明、配置对象和纯数据文件都没有需要验证的行为。如果文件不包含函数、逻辑或转换,就跳过测试。测试行为,而不是数据。
测试隔离与设置
- 清理逻辑应该放在哪里? 分层思考:配置消除整个类别的错误(在 vitest.config.ts 中清理 mock),设置文件处理项目级事项(自定义匹配器、全局 mock),beforeEach 处理测试专属状态。每个测试都自行清理 mock,就像每个函数都自行检查 null 一样——虽然可行,却没有抓住要点。将关注点放到适当的最高层级。
- 此测试是否依赖之前的测试或共享状态? 测试套件是彼此独立的平行宇宙——无论每个测试最先运行、最后运行还是单独运行,它都应该表现一致。状态依赖会产生“量子测试”,其通过或失败取决于执行顺序。如果一个测试需要另一个测试的数据,那么它们实际上是被人为拆分的同一个测试。
测试什么
- 我测试的是行为还是实现? 测试用户体验到的内容(输入 → 输出),而不是代码如何实现(调用了哪些函数)。实现测试会在安全重构时失效。
- 我能使用的最简单依赖是什么? 真实实现 > 假实现 > 桩 > 间谍 > 模拟。沿着这个层级逐步往下,每一步都会增加脆弱性。只有在使用真实代码不切实际时(外部 API、耗时操作),才使用模拟。
测试清晰度
- 别人能在 5 秒内理解此测试吗? 遵循 AAA 模式(Arrange、Act、Assert),明确划分各个阶段。如果设置过程复杂,就提取到名称清晰的辅助函数中。
- 同一行为是否有多种变体? 使用
it.each()编写参数化测试,而不是复制测试结构。每个概念只写一个断言,让测试保持专注。
性能与维护
- 6 个月后,此测试仍然有价值吗? 避免测试框架内部机制或琐碎操作。专注于真正能防止错误的业务逻辑、边界情况和错误处理。
- 此测试是否足够快,能在每次保存时运行? 避免在测试中执行昂贵操作。对数据库使用假实现,用模拟计时器处理延迟,并为外部调用设置桩。测试应在毫秒内完成。
此技能涵盖的内容
vitest 测试模式专家指南:
- 组织 - 文件位置、命名、describe 块结构
- AAA 模式 - Arrange、Act、Assert,一目了然
- 参数化测试 - 使用
it.each()减少重复 - 错误处理 - 测试异常、边界情况、故障注入
- 断言 - 使用严格断言捕获意外值
- 测试替身 - 假实现、桩、模拟、间谍的层级及各自的使用场景
- 异步测试 - Promises、async/await、计时器、并发测试
- 性能 - 通过高效设置和全局配置加快测试
- Vitest 功能 - 覆盖率、监视模式、设置文件、配置发现
- 快照测试 - 何时快照有助于维护,何时会造成阻碍
- 基于属性的测试 - 使用 fast-check 和生成的输入增强覆盖率
使用方法
此技能采用渐进式披露结构,以尽量减少上下文用量:
1. 从概览(AGENTS.md)开始
阅读 AGENTS.md,了解所有规则的简要概览(每条规则均附一行摘要),以及发现现有测试配置的工作流。
2. 检查现有测试配置
编写测试前:
- 首先检查
vitest.config.ts中的全局模拟清理设置(clearMocks、mockReset、restoreMocks) - 然后搜索设置文件(
test/setup.ts、vitest.setup.ts等),并分析其配置 - 查看 AGENTS.md 中的工作流
3. 按需加载特定规则
使用以下明确触发条件,判断何时加载每个参考文件:
必须加载(加载整个文件):
- 编写使用 Promise 或计时器的异步测试 → async-testing.md
- 使用模拟、桩、间谍或伪造对象 → test-doubles.md
- 审查现有测试文件 → property-based-testing.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
- 编码/解码配对、验证器、规范化器或纯函数 → property-based-testing.md
- 包含不变量、数学性质或数据转换的代码 → property-based-testing.md
- 已有 fast-check 或基于属性的测试 → property-based-testing.md
除非确有需要,否则不要加载:
- 如果测试运行很快(<50ms),不要加载 performance.md
- 除非提到快照,否则不要加载 snapshot-testing.md
- 编写基础测试时,不要加载 vitest-features.md
4. 应用模式
每个参考文件都包含:
- ❌ 展示反模式的错误示例
- ✅ 展示最佳实现的正确示例
- 关于该模式为何重要的说明
5. 使用报告模板
通过此技能审查测试代码时,请使用标准化报告格式:
模板: assets/output-report-template.md
报告格式包括:
- 执行摘要及测试质量影响评估
- 用于确定优先级的严重程度级别(Critical、High、Medium、Low)
- 影响分析(测试可靠性、可维护性、性能、清晰度)
- 分类(测试组织、断言、测试替身、异步测试、性能)
- 指向 references/ 中详细指南的模式引用
- 用于跟踪所有问题的汇总表
何时使用报告模板:
- 通过
/accelint-ts-testing <path>直接调用技能 - 用户要求跨一个或多个文件“审查测试代码”或“审计测试”,从而隐式调用技能
何时不使用报告模板:
- 用户要求“为此函数编写一个测试”(直接实现)
- 用户询问“这个测试有什么问题?”(回答问题)
- 用户要求修复特定测试(直接应用修复,不生成正式报告)
重要:审计测试时,务必检查基于属性的测试机会
- 每次审计都要加载 property-based-testing.md
- 遵循 AGENTS.md 中的“工作流:测试代码审查/审计”
- 检查高价值的 PBT 模式:编码/解码对、规范化器、验证器、纯函数、排序函数
- 即使未发现其他问题,也要在审计报告中列出 PBT 机会
快速示例
请参阅 quick-start.md,了解完整的前后对比示例,展示此技能如何将含糊不清的测试改进为清晰、易维护的测试。
评论
支持 Markdown