--- name: test-driven-development description: >- 测试驱动开发纪律——实现功能、修复 bug 或改变行为前,沿已批准的公共测试落点先写失败测试,确认有效红后最小实现转绿。批准的前置或收尾纯重构用行为测试保绿。例外按单点清单与可追溯授权处理。 --- > 输出语言:用户显式指定(含平台语言设置)优先,其次跟随近期对话,否则使用英语。新产物使用创建时语言,增量修改沿用原文语言;固定话术按语义表达。 > **外部搜索统一入口**:外部检索先用 anysearch;不可用时按[搜索与降级规则](../requirement-analysis/references/exploration-patterns.md)执行。 # 测试驱动开发(TDD) ## 概述 行为变化先确认有效红,再用最小实现转绿;纯重构用行为保护保绿。 **核心原则**:有效红证明断言能捕获目标差异;编译、环境故障和零测试不能替代它。 本 skill 只依赖项目自身的测试命令,不依赖任何平台专属工具,Claude Code 与 Codex 通用。 ## 何时使用 **总是**:新功能、bug 修复、行为变更走红绿循环;纯重构走本文「收尾纯重构」,不伪造红。 **例外清单(单点定义)**:一次性原型、生成代码、配置文件,以及不改变行为、契约或测试预期的纯文案。例外需要可追溯的用户授权;同一范围内已授权则引用原决定,不反复询问。技能规则、API 语义与测试预期即使写在 Markdown 中,也不是纯措辞。 ## 测试落点确认与消费 测试落点(seam)是观察公共行为的契约边界,可为模块函数、CLI、HTTP API、数据库适配器,不限于最外层界面。优先复用已有落点,选择仍能稳定观察目标行为的较高层接口,尽量少建新接口;一个落点可覆盖多个 Scenario,不逐私有函数制造测试。 spec/plan 已批准的落点直接使用,不重复确认。存量无 seam 标签但批准的精确接口和 Scenario 唯一指向同一边界时,只提取已有决定并记录来源,不迁移旧计划格式或推测新决定。 没有唯一落点或 spec/plan 冲突时,停止受影响任务的测试和实现,主线程走原澄清/偏差流程,不默默择一覆盖。即兴任务在写测试前确认公共接口和落点,quick-fix 并入已有修复确认环节,一次一题。implementer 回报 blocked,不向用户另开门、不改 plan 或扩大 writes。 ## 铁律 ``` 没有有效失败测试,就不写新增或改变行为的生产代码 ``` 未按纪律先写了行为实现?删除本次未经测试先行的实现,从失败测试重做,不留作参考;不删除无关既有工作。纯重构与已授权例外按对应规则处理,不冒充新增行为的红证据。 ## 红绿循环 ```dot digraph tdd_cycle { rankdir=LR; red [label="红\n写失败测试", shape=box, style=filled, fillcolor="#ffcccc"]; verify_red [label="确认失败\n原因正确", shape=diamond]; green [label="绿\n最小实现", shape=box, style=filled, fillcolor="#ccffcc"]; verify_green [label="确认通过\n全绿", shape=diamond]; next [label="下一个", shape=ellipse]; red -> verify_red; verify_red -> green [label="是"]; verify_red -> red [label="失败原因\n不对"]; green -> verify_green; verify_green -> green [label="否"]; verify_green -> next [label="是"]; next -> red; } ``` ### 红——写失败测试 在获批 seam 写一个最小测试:一个公共行为、清晰命名、独立真值。mock 准入遵循 references/testing-anti-patterns.md,不用自己的算法重算期望值。 ```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); }); // ❌ 坏:名字含糊、测的是 mock 不是代码 test('retry works', async () => { const mock = jest.fn() .mockRejectedValueOnce(new Error()) .mockRejectedValueOnce(new Error()) .mockResolvedValueOnce('success'); await retryOperation(mock); expect(mock).toHaveBeenCalledTimes(3); }); ``` ### 确认红——看它失败 **行为红绿路径的强制步骤。** 确认失败源于目标行为缺失,而非拼写、编译或环境故障;刻画已有行为遵循「收尾纯重构」,不冒充红证据。 - **行为缺陷测试直接通过了?** 核实是否复现目标缺陷并修正无效测试。纯重构刻画应先观察当前行为通过,不适用本条。 - **测试报错了?** 修到它以正确原因失败为止。 ### 绿——最小实现 写让测试通过的最简代码。不加多余功能、不顺手重构别的代码、不超出测试要求"改进"。 ```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'); } // ❌ 坏:maxRetries/backoff/onRetry 没人要 —— YAGNI ``` ### 确认绿——看它通过 **强制步骤。** 运行测试,确认:目标测试通过、其他测试仍通过、输出干净(无报错无警告)。 - **公共行为回归?** 修实现。已核实行为未变但测试绑定私有细节时,按反模式 7 修正落点,不迁就脆弱断言。 - **其他测试挂了?** 现在就修。 ### 重构候选 发现重复、命名或结构清理机会时,记录位置、理由与保护证据,交既有收尾,不插入每次红绿循环。每票第五步仍为提交;契约自检只查 over/under-building 与接口锚定。 ### 重复 为下一个行为写下一个失败测试。 ## 批准的前置纯重构与集成组 批准计划中解除实际实施阻碍的前置纯重构在 T01 执行,复用下方前后公共行为保护规则;不能等到收尾才解除阻碍。执行中额外发现的重构机会仍交收尾,不借此前置规则扩大授权。 集成组不是 TDD 例外:行为变化在实现前仍获得有效行为失败;纯机械/行为保持迁移先取得组基线保护。每票照常跑能运行的检查,仅对计划逐项声明的命令/范围/原因延后整体绿,保存真实 exit 与待验状态。意外局部失败阻塞,环境/编译错误不能当行为红。没有足够保护或无法获得行为红时回到设计,不把该票改名“重构”来跳过。 组员的公共行为最终在唯一组验证票上检验,普通功能票仍保持五步和有效红绿。没有有效红的纯重构与组员/组验证票不派给普通 implementer,不改变 implementation-result phase 或原接收条件。组验证完成不替代最终特性审查、相关回归和收尾全量。 ## 收尾纯重构 先核实授权范围及公共行为不变,再用已有行为测试记录调整前后的通过证据;缺保护时,先依据现行契约补行为刻画并观察当前实现通过,再结构调整。刻画通过不是红证据;若当前行为与需求不同,转缺陷复现红绿路径。 计划中的前置纯重构与组迁移按已批准时机执行,额外候选由现有收尾审查接收,收尾修复后复审受影响维度;quick-fix 使用自身收尾,不强制开启可选 acceptance-qa。处置沿已有授权边界。implementer 无法提供有效红证据的纯重构票回报 blocked,由主线程按现有串行分流处理,不改结果 schema 或普通实施票的红绿条件。 ## 路径与证据核对 行为变化采用有效红→最小实现→绿;纯重构采用前后公共行为保护;例外只按单点清单和可追溯授权。 未经测试先行的行为实现仍按本文铁律处理;不删除无关既有工作。 证据及完整完成条件见下节,不以重复劝诫替代实际执行。 ## 完成前检查清单 - [ ] 获批 seam 的公共行为、相关 Scenario、边缘与错误路径已有覆盖,不按新增函数数量要求直测 - [ ] 新增/改变行为的测试观察到有效红;纯重构另有前后绿,刻画不冒充红 - [ ] 红证据源于行为缺失,编译/环境故障、零测试、SKIP 不当 PASS - [ ] 行为红绿使用最小实现;纯重构保持已有契约 - [ ] 目标及相关测试通过,计划最终全量和范围外失败裁决保持 - [ ] 输出干净(无报错、无警告) - [ ] 测试验证真实行为,mock 遵循 testing-anti-patterns 的准入与声明依赖边界 - [ ] 边缘情况与错误路径已覆盖 按适用路径核对,不把其他路径的证据冒充自己的完成依据;例外有授权来源,重构候选已交收尾。 ## 调试集成 发现 bug?先写复现它的失败测试,再走 TDD 循环。测试既证明修复又防止回归。例外仅沿本文唯一清单与可追溯授权,不因“简单”自行豁免。 ## Mock 与测试反模式 添加 mock、测试工具方法、或想给生产类加测试专用方法时,先读 [testing-anti-patterns.md](references/testing-anti-patterns.md):不测 mock 的行为、不给生产类加测试专用方法、不在不理解依赖链时 mock、mock 必须镜像真实结构的完整字段。 ## 最终规则 ``` 新增或改变行为 → 有效红 → 最小实现 → 绿 纯重构 → 行为保护 → 结构调整 → 保持绿 例外 → 唯一清单及可追溯授权;普通实施票的红绿条件不变 ``` 未经用户允许,没有例外。