跳至正文

🧪 測試驅動開發(TDD)

📖 定義

TDD(測試驅動開發)是一種先編寫測試,然後編寫程式碼以通過這些測試的開發方法。它遵循紅-綠-重構循環,提高程式碼品質,減少錯誤,使重構更安全。單元測試獨立驗證單個函數或元件。

🎯 簡單類比

先畫藍圖

傳統開發
1. 開始建房子
2. 完成後檢查
3. 發現問題 → 需要大修改
4. 成本增加

TDD
1. 畫藍圖(編寫測試)
2. 按藍圖建造(編寫程式碼)
3. 驗證(執行測試)
4. 改進(重構)
5. 安全準確

⚙️ 運作原理

TDD循環 (紅-綠-重構)

🔴 紅(失敗)
└─ 編寫測試 → 失敗(無程式碼)

🟢 綠(通過)
└─ 編寫最少程式碼 → 測試通過

🔵 重構(改進)
└─ 改進程式碼 → 測試仍然通過

重複 → 逐步改進

💡 關鍵範例

基本TDD範例

// ========== 1. 紅: 編寫測試(失敗) ==========
test('add函數相加兩個數字', () => {
expect(add(2, 3)).toBe(5);
});
// FAIL - add未定義

// ========== 2. 綠: 最少程式碼(通過) ==========
function add(a, b) {
return a + b;
}
// PASS ✅

// ========== 3. 重構: 改進(如需要) ==========
// 程式碼簡單,無需改進

Jest單元測試

// user.js
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}

isValidEmail() {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(this.email);
}
}

// user.test.js
describe('User', () => {
let user;

beforeEach(() => {
user = new User('張三', 'zhang@example.com');
});

test('有效信箱應回傳true', () => {
expect(user.isValidEmail()).toBe(true);
});

test('無效信箱應回傳false', () => {
user.email = 'invalid-email';
expect(user.isValidEmail()).toBe(false);
});
});

非同步測試

test('非同步函數返回資料', async () => {
const data = await fetchData();
expect(data).toEqual({ name: 'John' });
});

模擬

// 函數模擬
const mockCallback = jest.fn(x => x * 2);

[1, 2, 3].forEach(mockCallback);

expect(mockCallback).toHaveBeenCalledTimes(3);

// 模組模擬
jest.mock('axios');

test('fetchUser返回使用者資料', async () => {
axios.get.mockResolvedValue({ data: { id: 1 } });
const user = await fetchUser(1);
expect(user).toEqual({ id: 1 });
});

🤔 常見問題

Q1. TDD的優點?

A:

優點:
1. 減少錯誤
2. 安全重構
3. 程式碼品質提高
4. 文件化
5. 信心

缺點:
1. 初始時間投資
2. 學習曲線
3. 測試維護

結論: 長期收益

Q2. 應該測試什麼?

A:

// ✅ 應該測試
1. 業務邏輯
2. 邊緣情況
3. 錯誤處理
4. 公共API

// ❌ 不需要測試
1. 外部函式庫
2. 簡單getter/setter
3. 私有實作細節

Q3. 測試覆蓋率目標?

A:

# 測量覆蓋率
npm test -- --coverage

# 目標:
80%以上覆蓋率 // 現實目標
100%覆蓋率 // 理想但不現實

# 記住:
高覆蓋率 ≠ 好測試
有意義的測試很重要!

🎬 總結

TDD是軟體品質的基礎:

  • 紅-綠-重構: TDD核心循環
  • 單元測試: 快速且隔離
  • 測試優先: 測試驅動設計
  • 持續改進: 重構的安全網

測試是對未來自己的投資! 🧪✨