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,善用它來提升您的開發實踐!