Files
2025-11-29 18:03:43 +08:00

7.1 KiB
Raw Permalink Blame History

description
description
讓測試通過Green 階段)。用最簡單、甚至是"錯誤"的方式快速讓測試變綠。

TDD Green - 讓測試通過Green

用最簡單的方式讓測試通過。可以作弊、可以硬編碼、可以"錯誤"。

【功能名】{{feature_name}}

Kent Beck 的 Green 階段理念

"快速讓測試通過,用任何手段。" "乾淨的程式碼是目標,但先讓它能動。"

三種讓測試通過的策略

Kent Beck 在書中提出的三種策略:

策略 1Fake It假實作

最快速的方式:回傳常數

// 測試
test('5 * 2 = 10', () => {
  expect(multiply(5, 2)).toBe(10);
});

// 假實作:直接回傳 10
function multiply(a, b) {
  return 10;
}

為什麼這樣做?

  • 快速得到綠燈
  • 心理上的安全感
  • 用下一個測試來逼出真正的實作

策略 2Obvious Implementation明顯實作

當實作很明顯時,直接寫出來

// 測試
test('加法運算', () => {
  expect(add(2, 3)).toBe(5);
});

// 明顯實作:加法很簡單
function add(a, b) {
  return a + b;
}

何時使用?

  • 實作非常簡單
  • 你很有信心
  • 不需要三角測量

策略 3Triangulation三角測量

用多個測試來推導正確實作

// 第 1 個測試
test('1 + 1 = 2', () => {
  expect(add(1, 1)).toBe(2);
});

// 假實作
function add(a, b) {
  return 2;
}

// 第 2 個測試(三角測量)
test('2 + 3 = 5', () => {
  expect(add(2, 3)).toBe(5);
});

// 被逼出真正的實作
function add(a, b) {
  return a + b;
}

何時使用?

  • 不確定正確的實作方式
  • 想從多個角度驗證
  • 設計還不清楚

實作步驟

1. 選擇策略

根據你的信心程度:

信心很低 → Fake It假實作
信心中等 → Triangulation三角測量
信心很高 → Obvious Implementation明顯實作

2. 寫最少的程式碼

重點:最少

// 測試需要一個類別
test('Dollar 乘法', () => {
  const five = new Dollar(5);
  const product = five.times(2);
  expect(product.amount).toBe(10);
});

// 最少的實作
class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    return new Dollar(10); // 假實作!
  }
}

3. 執行測試

npm test

看到綠燈! 🟢

4. 更新 journey.md

#### 🟢 Green - 讓測試通過

策略:{Fake It / Obvious / Triangulation}

實作說明:
{簡述你做了什麼}

程式碼位置:{檔案路徑}

測試結果:✅ 通過

#### 🤔 反思
- 這個實作明顯是假的/硬編碼的嗎?
- 需要下一個測試來逼出真正的實作嗎?
- 有沒有重複的程式碼?

#### 📝 下一步
執行 /kb-refactor 重構

Fake It 的威力

範例Money 的演進

第 1 輪:完全假實作

test('5 * 2 = 10', () => {
  const five = new Dollar(5);
  expect(five.times(2).amount).toBe(10);
});

class Dollar {
  constructor(amount) {
    this.amount = amount;
  }
  times(multiplier) {
    return new Dollar(10); // 硬編碼!
  }
}

第 2 輪:三角測量逼出真實作

test('5 * 3 = 15', () => {
  const five = new Dollar(5);
  expect(five.times(3).amount).toBe(15);
});

class Dollar {
  times(multiplier) {
    return new Dollar(this.amount * multiplier); // 真實作!
  }
}

常見的"作弊"技巧

技巧 1回傳常數

function getWelcomeMessage() {
  return "Hello, World!"; // 先硬編碼
}

技巧 2複製測試資料

// 測試
expect(processData(input)).toEqual({
  status: 'success',
  count: 5
});

// 實作:直接回傳期待值
function processData(input) {
  return { status: 'success', count: 5 };
}

技巧 3最簡單的 if

// 測試 1
test('even number', () => {
  expect(classify(2)).toBe('even');
});

// 假實作
function classify(n) {
  if (n === 2) return 'even';
}

// 測試 2 會逼出真實作
test('another even number', () => {
  expect(classify(4)).toBe('even');
});

function classify(n) {
  return n % 2 === 0 ? 'even' : 'odd';
}

實作的禁忌

Kent Beck 說明在 Green 階段應該避免的:

不要過度設計

// ❌ 不要這樣(太複雜)
class Dollar {
  constructor(amount, currency = 'USD') {
    this.amount = amount;
    this.currency = currency;
  }

  times(multiplier) {
    // 處理多幣別、匯率轉換...
  }

  // 一堆還不需要的方法
  add() {}
  subtract() {}
  convert() {}
}

// ✅ 要這樣(夠用就好)
class Dollar {
  constructor(amount) {
    this.amount = amount;
  }

  times(multiplier) {
    return new Dollar(this.amount * multiplier);
  }
}

不要一次實作多個測試

// 目前只有一個測試需要 times()
// 不要順便實作 add(), subtract()
// 等有測試需要時再加

不要重構

// ❌ Green 階段不要重構
// 先讓測試通過
// 重構留給下一步

// ✅ 先這樣
function calculate(x) {
  return x * 2;
}

// 不要在這裡就改成
const MULTIPLIER = 2;
function calculate(x) {
  return x * MULTIPLIER;
}
// 重構留給 /kb-refactor

心態:接受"醜陋"的程式碼

Kent Beck 強調的心態轉變:

階段 1讓它能動 (Make it work) ← 現在在這裡
    ↓
階段 2讓它正確 (Make it right)
    ↓
階段 3讓它快速 (Make it fast)

Green 階段只追求"能動"

  • 硬編碼?沒關係
  • 重複?沒關係
  • 醜陋?沒關係

下一步會改善

範例完整流程

測試(失敗)

test('購物車初始總價為 0', () => {
  const cart = new ShoppingCart();
  expect(cart.getTotal()).toBe(0);
});

實作策略選擇

信心評估:這個很簡單,用 Obvious Implementation

實作程式碼

// src/shopping-cart.js

class ShoppingCart {
  getTotal() {
    return 0; // 最簡單的實作
  }
}

module.exports = ShoppingCart;

執行測試

npm test
# ✅ 通過!

記錄到 journey.md

#### 🟢 Green - 讓測試通過

策略Obvious Implementation

實作說明:
建立 ShoppingCart 類別getTotal() 回傳 0。
雖然是硬編碼,但足以通過目前的測試。

程式碼位置src/shopping-cart.js

測試結果:✅ 通過

#### 🤔 反思
- 實作很簡單,是硬編碼
- 下一個測試:加入商品後總價應該改變
- 目前沒有明顯重複

#### 📝 下一步
執行 /kb-refactor 檢查是否需要重構

速度的重要性

Kent BeckGreen 階段要快

目標

  • 幾秒鐘到幾分鐘
  • 不要花太久時間
  • 快速得到綠燈的心理回饋

如果卡住

  • 寫更小的測試
  • 用更假的實作
  • 回退重來

下一步

測試通過了?執行:

/kb-refactor

檢查是否有重複,進行重構!

記住 Kent Beck 的話

"讓測試通過的技巧是暫時降低對程式碼品質的標準。" "骯髒的程式碼是通往乾淨程式碼的墊腳石。" "當你看到綠燈時,那是重構的信號。"