
name: test-driven-developmentdescription: “Use when implementing any feature or bugfix, before writing implementation code”risk: criticalsource: communitydate_added: “2026-02-27”测试驱动开发TDD概述先写测试。看着它失败。编写最少的代码来通过。核心原则如果你没有看到测试失败你就不知道它是否测试了正确的东西。违反规则的字面意思就是违反规则的精神。何时使用始终新功能缺陷修复重构行为更改例外情况询问你的合作伙伴一次性原型生成的代码配置文件想着就这一次跳过 TDD停下来。那是合理化。铁律没有先写失败的测试就没有生产代码在测试之前写了代码删掉它。重新开始。没有例外不要把它留作参考不要在写测试时改编它不要看它删除就是删除从测试重新实现。就这样。红-绿-重构digraph tdd_cycle { rankdirLR; red [labelRED\nWrite failing test, shapebox, stylefilled, fillcolor#ffcccc]; verify_red [labelVerify fails\ncorrectly, shapediamond]; green [labelGREEN\nMinimal code, shapebox, stylefilled, fillcolor#ccffcc]; verify_green [labelVerify passes\nAll green, shapediamond]; refactor [labelREFACTOR\nClean up, shapebox, stylefilled, fillcolor#ccccff]; next [labelNext, shapeellipse]; red - verify_red; verify_red - green [labelyes]; verify_red - red [labelwrong\nfailure]; green - verify_green; verify_green - refactor [labelyes]; verify_green - green [labelno]; refactor - verify_green [labelstay\ngreen]; verify_green - next; next - red; }红 - 编写失败测试编写一个显示预期结果的最小测试。typescript test(retries failed operations 3 times, async () { let attempts 0; const operation () { attempts; if (attempts 3) throw new Error(fail); return success; };const result await retryOperation(operation);expect(result).toBe(‘success’);expect(attempts).toBe(3);});清晰的名称测试真实行为只测一件事 /Good Bad typescript test(retry works, async () { const mock jest.fn() .mockRejectedValueOnce(new Error()) .mockRejectedValueOnce(new Error()) .mockResolvedValueOnce(success); await retryOperation(mock); expect(mock).toHaveBeenCalledTimes(3); });名称模糊测试 mock 而非代码要求一种行为清晰的名称真实代码除非不可避免否则不使用 mock验证红 - 看着它失败强制要求。绝不跳过。npmtestpath/to/test.test.ts确认测试失败不是报错失败消息符合预期因为功能缺失而失败不是打字错误测试通过了你在测试已存在的行为。修正测试。测试报错了修正错误重新运行直到它正确地失败。绿 - 最小代码编写能通过测试的最简单代码。typescript async function retryOperation(fn: () Promise): Promise { for (let i 0; i 3; i) { try { return await fn(); } catch (e) { if (i 2) throw e; } } throw new Error(unreachable); } 刚好够通过 typescript async function retryOperation( fn: () Promise, options?: { maxRetries?: number; backoff?: linear | exponential; onRetry?: (attempt: number) void; } ): Promise { // YAGNI } 过度设计不要添加功能、重构其他代码或在测试之外改进。验证绿 - 看着它通过强制要求。npmtestpath/to/test.test.ts确认测试通过其他测试仍然通过输出干净无错误、无警告测试失败了修代码不是修测试。其他测试失败了立即修复。重构 - 清理仅在变绿之后消除重复改进名称提取辅助函数保持测试通过。不要添加行为。重复为下一个功能编写下一个失败的测试。好测试质量好差最小化只测一件事。名称里有和拆开。test(validates email and domain and whitespace)清晰名称描述行为test(test1)显示意图展示期望的 API掩盖代码应该做什么为什么顺序很重要“我先写测试然后再验证它能工作”事后编写的测试会立即通过。立即通过证明不了什么可能测试了错误的东西可能测试实现而非行为可能漏掉你忘记的边界情况你从没见过它捕捉到 bug测试优先迫使你看到测试失败证明它确实测试了某些东西。“我已经手动测试了所有边界情况”手动测试是临时的。你以为你测试了所有东西但是没有你测试过什么的记录代码改变时无法重新运行压力下容易忘记情况“我试过它能用” ≠ 全面自动化测试是系统化的。它们每次都运行相同的方式。“删除 X 小时的工作是浪费”沉没成本谬误。时间已经过去了。你现在可以选择删除并用 TDD 重写再花 X 小时高信心保留并事后添加测试30 分钟低信心可能有 bug浪费是保留你无法信任的代码。没有真正测试的工作代码是技术债务。“TDD 是教条的务实意味着适应”TDD 就是务实的在提交前发现 bug比事后调试更快防止回归测试立即捕捉破坏记录行为测试展示如何使用代码启用重构自由更改测试捕捉破坏务实的捷径 在生产环境调试 更慢。“事后测试达到相同目标 - 这是精神而非仪式”不。事后测试回答这段代码做什么“测试优先回答这段代码应该做什么”事后测试受到你的实现偏见的影响。你测试你构建的东西而不是需要的东西。你验证你记得的边界情况而不是发现的那些。测试优先迫使你在实现之前发现边界情况。事后测试验证你记得所有东西你没有。事后 30 分钟测试 ≠ TDD。你获得了覆盖率却失去了测试有效的证明。常见的合理化借口现实“太简单不需要测试”简单的代码也会坏。测试只需 30 秒。“我之后会测试”立即通过的测试证明不了什么。“事后测试达到相同目标”事后测试 “这做什么” 测试优先 “这该做什么”“已经手动测试过了”临时的 ≠ 系统化的。没有记录无法重新运行。“删除 X 小时是浪费”沉没成本谬误。保留未验证的代码是技术债务。“留作参考先写测试”你会改编它。那算事后测试。删除就是删除。“需要先探索”可以。扔掉探索从 TDD 开始。“测试难 设计不清楚”倾听测试。难测试 难使用。“TDD 会拖慢我”TDD 比调试更快。务实 测试优先。“手动测试更快”手动无法证明边界情况。每次更改你都要重新测试。“现有代码没有测试”你正在改进它。为现有代码添加测试。红旗 - 停下来重新开始测试之前的代码实现之后的测试测试立即通过无法解释测试为什么失败稍后添加的测试合理化就这一次“我已经手动测试过了”“事后测试达到相同目的”“这是关于精神而非仪式”“留作参考或改编现有代码”“已经花了 X 小时删除是浪费”“TDD 是教条的我很务实”“这不同因为…”所有这些都意味着删除代码。用 TDD 重新开始。示例缺陷修复缺陷空的电子邮件被接受红test(rejects empty email,async(){constresultawaitsubmitForm({email:});expect(result.error).toBe(Email required);});验证红$npmtestFAIL: expectedEmail required, got undefined绿functionsubmitForm(data:FormData){if(!data.email?.trim()){return{error:Email required};}// ...}验证绿$npmtestPASS重构如果需要为多个字段提取验证。验证检查清单在标记工作完成之前每个新函数/方法都有测试实现前看到了每个测试失败每个测试因预期原因失败功能缺失而非打字错误编写了最少的代码来通过每个测试所有测试通过输出干净无错误、无警告测试使用真实代码除非不可避免否则不使用 mock边界情况和错误已覆盖不能勾选所有框你跳过了 TDD。重新开始。卡住时问题解决方案不知道如何测试写下期望的 API。先写断言。询问你的合作伙伴。测试太复杂设计太复杂。简化接口。必须 mock 一切代码耦合太紧。使用依赖注入。测试设置巨大提取辅助函数。仍然复杂简化设计。调试集成发现 bug编写复现它的失败测试。遵循 TDD 循环。测试证明修复并防止回归。绝不无测试地修复 bug。测试反模式添加 mock 或测试工具时阅读 testing-anti-patterns.md 以避免常见陷阱测试 mock 行为而非真实行为在生产类中添加仅测试使用的方法不理解依赖就进行 mock最终规则生产代码 → 测试已存在且先失败 否则 → 不是 TDD未经你的合作伙伴许可没有例外。局限性仅当任务明确符合上述范围时使用此技能。不要将输出视为环境特定验证、测试或专家审查的替代品。如果缺少必需的输入、权限、安全边界或成功标准请停下来询问澄清。