TDD案例

wen java案例 1

本文目录导读:

TDD案例

  1. 案例需求:实现一个购物车类(ShoppingCart)
  2. 第一步:编写失败的测试(红灯)
  3. 第二步:编写最少代码让测试通过(绿灯)
  4. 第三步:重构(Refactor)
  5. 测试运行结果
  6. TDD 核心三步骤回顾
  7. 什么时候用 TDD?

下面我给你一个完整的 TDD(测试驱动开发) 实战案例,我会带你走一遍完整的 红-绿-重构 循环。


案例需求:实现一个购物车类(ShoppingCart)

功能要求:

  1. 可以添加商品(商品有名称和单价)
  2. 可以移除商品
  3. 可以计算总价(支持折扣逻辑:满 100 减 10)
  4. 可以获取商品数量

第一步:编写失败的测试(红灯)

先写测试,再写实现代码。

// 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 案例吗?

抱歉,评论功能暂时关闭!