name: tdd description: “测试驱动开发工作流,采用RED-GREEN-REFACTOR方法,借鉴自Kent Beck、Michael Feathers和Ousterhout的反驳观点” tags:
- 测试
- 工作流
- 方法论
- tdd
测试驱动开发 (TDD)
节奏:RED-GREEN-REFACTOR
1. RED - 首先编写失败的测试(定义预期行为)
2. GREEN - 最小实现以通过(不要过度设计)
3. REFACTOR - 清理、消除重复,再次运行测试
为什么TDD有效(传说)
Kent Beck(《测试驱动开发:示例》)
“编写单元测试的行为更像是设计行为而非验证行为。”
- 测试成为意图的可执行文档
- “假装直到成功” - 从硬编码值开始,逐步泛化
- 小步骤减少调试时间
- 重构的信心来自测试覆盖
Michael Feathers(《有效处理遗留代码》)
“我所知的最强大的功能添加技术是测试驱动开发。”
- TDD在面向对象和过程代码中都有效
- 先写测试强迫你思考接口
- 测试是安全网,使积极重构成为可能
- 遗留代码 = 没有测试的代码
Martin Fowler(《重构》)
“Kent Beck将先写测试的习惯融入称为测试驱动开发的技术中。”
- TDD依赖于短周期
- 测试使重构成为可能
- 重构变得安全 - 测试即时捕捉回归
反驳点:知道何时打破规则
John Ousterhout(《软件设计哲学》)
“测试驱动开发的问题在于它将注意力集中在让特定功能工作,而不是找到最佳设计。”
当TDD可能有害时:
- 可能导致战术编程(关注功能,而非设计)
- 可能产生易于测试但难以理解的代码
- 过度测试实现细节的风险
平衡:
- 对于探索性/架构工作,先设计,然后添加测试
- 不要让测试把你逼入角落
- 定期退一步评估整体设计
何时使用TDD
✅ 使用TDD于:
- 具有清晰需求的新功能
- Bug修复(首先编写复制bug的测试)
- 重构现有代码(首先添加特征化测试)
- API设计(测试揭示易用性)
- 任何将长期维护的代码
❌ 跳过TDD于:
- 探索性快速原型(但如果保留代码,之后添加测试)
- 紧急热修复(但立即之后添加测试)
- 纯UI/样式更改
- 一次性脚本
- 可丢弃原型
TDD工作流
# 1. 编写测试,观察失败
bun test src/thing.test.ts # RED - 测试失败
# 2. 实现最小代码以通过
bun test src/thing.test.ts # GREEN - 测试通过
# 3. 重构,测试仍然通过
bun test src/thing.test.ts # GREEN - 仍然通过
# 4. 重复下一个行为
TDD模式
从断言开始
首先编写断言,然后反向工作:
// 从这里开始
expect(result).toBe(42);
// 然后弄清楚'result'是什么
const result = calculate(input);
// 然后弄清楚'input'是什么
const input = { value: 21 };
三角剖分
使用多个示例驱动泛化:
it("doubles 2", () => expect(double(2)).toBe(4));
it("doubles 3", () => expect(double(3)).toBe(6));
// 现在你必须实现通用解决方案
明显实现
当解决方案明显时,直接编写:
function add(a: number, b: number): number {
return a + b; // 不要伪装这个
}
假装直到成功
当不确定时,从硬编码值开始:
// 第一遍
function fibonacci(n: number): number {
return 1; // 对于n=1通过
}
// 为n=2添加测试,然后泛化
测试金字塔
/\
/ \ E2E(少数)
/----\
/ \ 集成测试(一些)
/--------\
/ \ 单元测试(多数)
--------------
- 单元测试:快速、隔离,测试一件事
- 集成测试:测试组件交互
- E2E测试:测试完整用户流程(昂贵,谨慎使用)
常见TDD错误
- 一次编写太多测试 - 一次只处理一个失败测试
- 测试实现而非行为 - 测试什么,而非如何
- 跳过重构步骤 - 技术债务累积
- 过度模拟 - 不要模拟你不拥有的东西
- 测试私有方法 - 通过公共接口测试
与Beads集成
当处理bead时:
- 开始bead:
beads_start(id="bd-123") - 为需求编写失败测试
- 实现以通过
- 重构
- 关闭bead:
beads_close(id="bd-123", reason="完成:测试通过")