TTechSpecs

Test-Driven Development Skill

Test-Driven Development (TDD) 是 Superpowers 中最重要的執行型 skills 之一,專注於使用測試驅動的開發方法論來確保程式碼品質。

何時使用

在以下情況中使用 TDD skill:

需要使用的情境

  • 開始新功能開發
  • 重構現有程式碼
  • 需要高品質保證的核心功能
  • 不確定如何測試某個功能
  • 需要確保重構不破壞現有行為

不適合的情境

  • 緊急的 hotfix(可以先用 systematic-debugging)
  • 簡單的樣式調整
  • 純檔案或配置修改
  • 已經有完整實作的功能

TDD 的核心循環

Red-Green-Refactor 循環

TDD 遵循三個步驟的循環:

1. Red - 寫一個失敗的測試

先寫一個會失敗的測試,描述期望的行為:

// 測試描述:新增 Todo 功能
test('should add new todo', () => {
  const todo = { id: 1, text: 'Test todo', completed: false };
  // 期望這個 todo 能被成功新增
  expect(addTodo(todo)).toEqual([...existingTodos, todo]);
});

2. Green - 讓測試通過(最快方式)

寫最少的程式碼讓測試通過:

function addTodo(todo) {
  return [...todos, todo]; // 最簡單的實作
}

3. Refactor - 重構程式碼

在測試保護下重構和改進程式碼:

// 重構:使用唯讀方式
const addTodo = (todo: Todo, todos: ReadonlyArray<Todo>) => [
  ...todos,
  todo
];

如何使用 TDD Skill

觸發詞

「使用 test-driven-development skill 開發測試驅動開發」

實際範例

使用者輸入

使用 test-driven-development skill 為購物車功能開發測試和實作

AI 回應

讓我們使用 TDD 方法論開發購物車功能:

## 第 1 輪:Red - 寫測試

```typescript
describe('ShoppingCart', () => {
  describe('addItem', () => {
    it('should add item to empty cart', () => {
      const cart = new ShoppingCart();
      const item = { id: 1, name: 'Product A', price: 100 };
      
      cart.addItem(item);
      
      expect(cart.items).toHaveLength(1);
      expect(cart.items[0]).toEqual(item);
    });

    it('should calculate total correctly', () => {
      const cart = new ShoppingCart();
      cart.addItem({ id: 1, name: 'Product A', price: 100 });
      cart.addItem({ id: 2, name: 'Product B', price: 200 });
      
      expect(cart.total).toBe(300);
    });
  });
});

第 2 輪:Green - 最簡實作

class ShoppingCart {
  items: any[] = [];
  total: number = 0;

  addItem(item: any) {
    this.items.push(item);
    this.total += item.price;
  }
}

第 3 輪:Refactor - 改進設計

interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

class ShoppingCart {
  private items: Map<number, CartItem> = new Map();
  
  addItem(item: CartItem): void {
    const existing = this.items.get(item.id);
    if (existing) {
      this.items.set(item.id, {
        ...existing,
        quantity: existing.quantity + 1
      });
    } else {
      this.items.set(item.id, { ...item, quantity: 1 });
    }
  }

  getTotal(): number {
    return Array.from(this.items.values())
      .reduce((sum, item) => sum + item.price * item.quantity, 0);
  }
}

測試類型

1. 單元測試

測試單一功能點:

test('should mark todo as completed', () => {
  const todo = { id: 1, text: 'Test', completed: false };
  const completed = toggleTodo(todo);
  expect(completed.completed).toBe(true);
});

2. 整合測試

測試多個組件的協作:

test('should add and retrieve todo', () => {
  const list = new TodoList();
  list.add('Test todo');
  expect(list.getAll()).toContainEqual(
    expect.objectContaining({ text: 'Test todo' })
  );
});

3. 端對端測試

測試使用者流程:

test('should complete user flow', () => {
  render(<TodoApp />);
  
  fireEvent.change(screen.getByPlaceholder('輸入待辦事'), {
    target: { value: '新任務' }
  });
  fireEvent.click(screen.getByRole('button', { name: '新增' }));
  
  expect(screen.getByText('新任務')).toBeInTheDocument();
});

TDD 的優勢

1. 品質保證

確保功能正確

  • 每個功能都有對應的測試
  • 重構時不會破壞既有行為
  • 測試即為文件

2. 設計改善

強制思考設計

  • 先考慮如何測試,自然會考慮如何設計
  • 測試驅動更好的 API 設計
  • 降低複雜度

3. 開發信心

重構無恐懼

  • 有測試保護,重構更安全
  • 即時反饋,快速迭代
  • 降低引入 bug 的風險

常見問題

Q1:測試什麼時候寫?

A:在實作任何功能之前先寫測試。測試描述了功能的期望行為。

Q2:測試太花時間?

A:TDD 實際上節省時間,因為:

  • 減少除錯時間
  • 減少重複的手動測試
  • 重構更快更安全

Q3:如何測試 UI 元素?

A:使用 Testing Library(如 React Testing Library)測試使用者互動,而非實作細節。

與其他 Skills 組合

推薦組合

Writing Plans + Test-Driven Development

writing-plans → test-driven-development

在計畫階段就考慮測試策略

Test-Driven Development + Verification

test-driven-development → verification-before-completion

TDD 實作後進行最終驗證

最佳實踐

1. 保持測試簡單

  • 一個測試只測試一個行為
  • 測試名稱應清楚描述行為
  • 避免過複雜的測試邏輯

2. 測試邊界條件

test('should handle empty cart', () => {
  const cart = new ShoppingCart();
  expect(cart.getTotal()).toBe(0);
});

test('should handle invalid input', () => {
  expect(() => cart.addItem(null)).toThrow();
});

3. 測試錯誤情況

test('should show error when adding duplicate item', () => {
  // 測試錯誤處理邏輯
});

進階技巧

1. 測試驅動重構

先用測試驗動重構,再實作新功能:

第 1 步:為新功能寫測試
第 2 步:調整現有測試(可能失敗)
第 3 步:實作新功能
第 4 步:所有測試通過

2. 行為驅動開發

從使用者行為出發設計測試:

使用者想要:標記項目為完成

測試:點擊完成按鈕後,項目顯示為已完成

實作:實作完成功能

3. 持續整合測試

不斷積累測試,確保系統整體品質:

新功能 → 單元測試 → 整合測試 → 端對端測試

TDD 是確保程式碼品質和長期可維護性的關鍵 skill,善用它來提升您的開發實踐!