--- name: shared/tdd description: 测试驱动开发(TDD)方法论,Red-Green-Refactor 循环,先写失败测试再写实现 version: 1.0.0 agent: shared type: methodology user-invocable: false agent-invocable: true dependencies: [] triggers: - 实现新功能前 - 修复 bug 前 - 重构代码前 - 任何行为变更前 metadata: internal: true --- # 测试驱动开发(TDD) ## 核心铁律 ``` 没有失败的测试,就不写生产代码。 ``` 如果先写了实现代码,**必须删除**,从测试重新开始。不保留"参考",不"边写测试边改"。 ## 适用场景 **必须使用 TDD**: - 新功能实现 - Bug 修复 - 代码重构 - 任何行为变更 **例外(需用户明确同意)**: - 一次性原型代码 - 纯配置文件 - 自动生成的代码 ## Red-Green-Refactor 循环 ### 🔴 RED — 写失败测试 为**一个行为**写最小测试: - 一个测试只测一件事(名称里有 "and" 就拆开) - 名称描述行为,而非实现(`rejects empty email` 而非 `test1`) - 用真实代码,除非依赖外部服务才 mock - 展示你期望的 API 长什么样 ```typescript // ✅ 好:测试真实行为 test('rejects empty email', () => { expect(validateEmail('')).toBe(false); }); // ❌ 坏:测试 mock 而非代码 test('email works', () => { const mock = jest.fn(); mock(''); expect(mock).toHaveBeenCalled(); }); ``` ### 验证 RED — 必须看到它失败 **这一步不可跳过。** 运行测试,确认: ```bash # 前端 npx jest path/to/test.test.ts # 后端 npm test -- path/to/test.test.ts ``` 确认三件事: 1. 测试**失败**(不是通过) 2. 失败原因是**功能缺失**(不是拼写错误、导入错误) 3. 失败信息符合预期 **如果测试直接通过** → 你在测已有行为,测试写错了,修正它。 **如果测试报错(error)** → 修复错误(如导入路径),重跑直到它正确失败。 ### 🟢 GREEN — 写最小实现 写**刚好让测试通过**的最少代码: - 不添加测试未要求的功能 - 不重构、不"顺便优化" - 不提前抽象 ```typescript // ✅ 好:刚好通过 function validateEmail(email: string): boolean { return email.trim().length > 0; } // ❌ 坏:过度工程 function validateEmail( email: string, options?: { allowPlus?: boolean; domainWhitelist?: string[] }, ): boolean { // YAGNI } ``` ### 验证 GREEN — 必须看到它通过 运行测试,确认: 1. 新测试通过 2. **所有已有测试仍然通过** 3. 输出干净(无报错、无意外警告) **如果新测试失败** → 修代码,不修测试。 **如果其他测试失败** → 立即修复。 ### 🔵 REFACTOR — 重构(仅在 GREEN 之后) 测试全绿后才允许重构: - 消除重复 - 改善命名 - 提取辅助函数 - **不新增行为** 重构后必须重新跑全部测试,保持全绿。 ### 循环 下一个行为 → 回到 RED。 ## Bug 修复 TDD 修复 bug 时,先写一个**复现 bug 的失败测试**: ``` 1. 写测试复现 bug → 确认失败(RED) 2. 修复代码 → 确认通过(GREEN) 3. 这个测试就是回归保护 ``` **禁止**在没有复现测试的情况下修复 bug。 ## 验证清单 标记任务完成前,逐项确认: - [ ] 每个新增函数/方法都有对应测试 - [ ] 每个测试都先看到它失败(RED 已验证) - [ ] 失败原因是功能缺失,而非拼写/导入错误 - [ ] 实现代码是让测试通过的最小集 - [ ] 全部测试通过 - [ ] 测试用真实代码(仅在不可避免时 mock) - [ ] 边界条件和错误路径已覆盖 **任何一项打不了勾 → 你跳过了 TDD,回到 RED 重新开始。** ## 常见借口与回应 | 借口 | 回应 | | ------------------------ | ------------------------------------ | | "太简单了不用测" | 简单代码也会坏,测试只要 30 秒 | | "我之后补测试" | 事后写的测试一跑就过,证明不了任何事 | | "我手动测过了" | 手动测试无记录、不可复跑、容易漏 | | "已经写了代码,删了浪费" | 沉没成本。保留未验证的代码才是技术债 | | "TDD 太慢" | TDD 比事后调试快,省的是未来的时间 | | "这个不一样因为..." | 没有例外。删代码,从测试开始。 | ## 卡住时 | 问题 | 解法 | | ------------- | ------------------------------- | | 不知道怎么测 | 先写你期望的 API 调用,再写断言 | | 测试太复杂 | 说明设计太复杂,简化接口 | | 什么都要 mock | 代码耦合太高,用依赖注入解耦 | | 测试设置庞大 | 提取辅助函数;仍复杂就简化设计 |