测试驱动开发(Test-Driven Development)是一种「测试先行」的软件开发方法。它要求你在编写功能代码之前,先编写一个会失败的测试。听起来反直觉,但实践者都说:TDD 改变了他们写代码的方式。
红-绿-重构循环
TDD 的核心是一个三步循环:
- 🔴 红:编写一个失败的测试,定义你期望的行为
- 🟢 绿:编写最少量的代码让测试通过
- 🔵 重构:在测试保护下改善代码结构
然后重复这个循环,每次只关注一个小功能点。
一个完整的 TDD 示例
假设我们要实现一个购物车总价计算功能:
第一步:写测试(红)
describe('ShoppingCart', () => {
it('should calculate total price of items', () => {
const cart = new ShoppingCart();
cart.addItem({ name: 'Book', price: 29.99 });
cart.addItem({ name: 'Pen', price: 3.50 });
expect(cart.getTotal()).toBe(33.49);
});
});
第二步:最简实现(绿)
class ShoppingCart {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
getTotal() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}
第三步:添加新测试,扩展功能
it('should apply discount when total exceeds 100', () => {
const cart = new ShoppingCart();
cart.addItem({ name: 'Laptop', price: 120 });
expect(cart.getTotal()).toBe(108); // 10% off
});
TDD 的实际收益
- 更好的设计:先写测试迫使你从使用者角度思考 API
- 即时反馈:每次改动都有测试验证,信心十足
- 活文档:测试用例展示了代码的预期行为
- 更少的 Bug:边界条件在写测试时就被考虑到
- 重构安全网:有测试覆盖,重构不再可怕
常见误解
- 「TDD 会降低开发速度」:短期看似慢,长期因减少调试和返工而更快
- 「要 100% 测试覆盖率」:关注关键路径和业务逻辑,不必追求数字
- 「TDD 只适用于单元测试」:TDD 可以应用于任何级别的测试
「TDD 的价值不在于测试本身,而在于它驱动你写出可测试的、设计良好的代码。」
如何开始
- 选择一个小的、独立的功能模块练手
- 从最简单的 happy path 测试开始
- 逐步添加边界条件和异常场景
- 坚持至少两周,让红-绿-重构成为肌肉记忆
- 在 Code Review 中分享你的 TDD 实践