本文目录导读:

- 案例需求:实现一个购物车类(ShoppingCart)
- 第一步:编写失败的测试(红灯)
- 第二步:编写最少代码让测试通过(绿灯)
- 第三步:重构(Refactor)
- 测试运行结果
- TDD 核心三步骤回顾
- 什么时候用 TDD?
下面我给你一个完整的 TDD(测试驱动开发) 实战案例,我会带你走一遍完整的 红-绿-重构 循环。
案例需求:实现一个购物车类(ShoppingCart)
功能要求:
- 可以添加商品(商品有名称和单价)
- 可以移除商品
- 可以计算总价(支持折扣逻辑:满 100 减 10)
- 可以获取商品数量
第一步:编写失败的测试(红灯)
先写测试,再写实现代码。
// shoppingCart.test.ts
import { ShoppingCart } from './shoppingCart';
describe('ShoppingCart', () => {
let cart: ShoppingCart;
beforeEach(() => {
cart = new ShoppingCart();
});
describe('添加商品', () => {
test('空购物车添加一个商品后,商品数量为 1', () => {
cart.addItem({ name: '苹果', price: 5 });
expect(cart.getItemCount()).toBe(1);
});
test('添加多个相同商品后,数量正确', () => {
cart.addItem({ name: '苹果', price: 5 });
cart.addItem({ name: '苹果', price: 5 });
cart.addItem({ name: '香蕉', price: 3 });
expect(cart.getItemCount()).toBe(3);
});
});
describe('移除商品', () => {
test('移除一个商品后,数量减少', () => {
cart.addItem({ name: '苹果', price: 5 });
cart.addItem({ name: '香蕉', price: 3 });
cart.removeItem('苹果');
expect(cart.getItemCount()).toBe(1);
});
});
describe('计算总价', () => {
test('没有折扣时,总价正确', () => {
cart.addItem({ name: '苹果', price: 5 });
cart.addItem({ name: '香蕉', price: 10 });
expect(cart.getTotalPrice()).toBe(15);
});
test('总价达到 100 元,享受满 100 减 10 优惠', () => {
// 10 个苹果,每个 10 元 = 100 元
for (let i = 0; i < 10; i++) {
cart.addItem({ name: '苹果', price: 10 });
}
expect(cart.getTotalPrice()).toBe(90);
});
test('总价未满 100 元,不享受优惠', () => {
cart.addItem({ name: '苹果', price: 99 });
expect(cart.getTotalPrice()).toBe(99);
});
});
});
⚠️ 此时运行测试,会报错:
Cannot find module './shoppingCart'— 这就是 红灯。
第二步:编写最少代码让测试通过(绿灯)
// shoppingCart.ts
export interface Item {
name: string;
price: number;
}
export class ShoppingCart {
private items: Item[] = [];
addItem(item: Item): void {
this.items.push(item);
}
removeItem(name: string): void {
this.items = this.items.filter(item => item.name !== name);
}
getItemCount(): number {
return this.items.length;
}
getTotalPrice(): number {
const total = this.items.reduce((sum, item) => sum + item.price, 0);
// 满 100 减 10
return total >= 100 ? total - 10 : total;
}
}
✅ 此时运行所有测试,全部通过 — 这就是 绿灯。
第三步:重构(Refactor)
重构阶段不能改变功能,只优化代码结构和可读性。
// 重构后的代码(逻辑不变,代码更优雅)
export class ShoppingCart {
private items: Item[] = [];
addItem(item: Item): void {
this.items.push(item);
}
removeItem(name: string): void {
this.items = this.items.filter(item => item.name !== name);
}
getItemCount(): number {
return this.items.length;
}
getTotalPrice(): number {
const total = this.getRawTotal();
return this.applyDiscount(total);
}
private getRawTotal(): number {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
private applyDiscount(total: number): number {
const DISCOUNT_THRESHOLD = 100;
const DISCOUNT_AMOUNT = 10;
return total >= DISCOUNT_THRESHOLD ? total - DISCOUNT_AMOUNT : total;
}
}
✅ 重构后再次运行测试,确认仍然全部通过。
测试运行结果
✓ 空购物车添加一个商品后,商品数量为 1
✓ 添加多个相同商品后,数量正确
✓ 移除一个商品后,数量减少
✓ 没有折扣时,总价正确
✓ 总价达到 100 元,享受满 100 减 10 优惠
✓ 总价未满 100 元,不享受优惠
Test Suites: 1 passed, 1 total
Tests: 6 passed, 6 total
TDD 核心三步骤回顾
| 阶段 | 行动 | 说明 |
|---|---|---|
| 🔴 红灯 | 先写失败的测试 | 验证测试框架能捕获缺失功能 |
| 🟢 绿灯 | 写最少代码让测试通过 | 不要写多余代码 |
| 🔵 重构 | 优化代码结构,保持功能不变 | 提高代码质量,测试保证安全 |
什么时候用 TDD?
- 逻辑复杂、容易出错的业务代码(如价格计算)
- 需要长期维护的核心模块
- 有明确输入输出的功能
注意: TDD 不适用于所有场景(如 UI 界面、原型探索期),要灵活运用。
需要我再给你一个 带边界条件测试 或 异步逻辑(如 API 调用) 的 TDD 案例吗?