TDD 節奏:紅-綠-重構 (Red-Green-Refactor)

★★★★★Draft

FromTDDClean Code

📚 From the Books

TDD 節奏:紅-綠-重構 (Red-Green-Refactor)Testing

TDD 不是「先寫測試」這麼簡單,而是一種由測試驅動設計的節奏:先寫一個會失敗的測試(紅),用最簡陋的方式讓它通過(綠),再回頭重構消除重複(重構)。每一圈都小到幾乎可笑——而正是這種小步伐,讓你隨時都站在可運作的地面上。

🧠 Why It Matters

TIP

什麼不用測?Phlip 的答案:「寫測試直到恐懼轉化為無聊。」該測的是你自己寫的條件判斷、迴圈、運算、多型;別去測你信任的第三方程式碼——除非你想用一個會失敗的測試來標記它的 bug 被修好了。

⚖️ 一圈紅綠重構

① 紅 — 先寫一個會失敗的測試(API 還不存在)
// 連 Money 都還沒寫,測試先定義「我希望怎麼用它」
test("5 USD times 2 is 10 USD", () => {
  const five = money(5, "USD");
  expect(five.times(2)).toEqual(money(10, "USD"));
});
// ▶ 執行 → 失敗(紅):money is not defined。這是預期中的失敗。
② 綠 — 用最小、甚至簡陋的實作讓它通過
// 不追求漂亮,只求綠燈。允許先寫死、之後再長出來。
function money(amount, currency) {
  return {
    amount,
    currency,
    times(n) {
      return money(amount * n, currency);
    },
  };
}
// ▶ 執行 → 通過(綠)。現在有安全網了。
③ 重構 — 綠燈保護下消除重複、改善設計
// 有了通過的測試,才敢動結構。例如把「相等比較」抽出來共用,
// 或把建構邏輯收斂——每改一步就重跑測試,始終保持綠。
class Money {
  constructor(amount, currency) {
    this.amount = amount;
    this.currency = currency;
  }
  times(n) {
    return new Money(this.amount * n, this.currency);
  }
  equals(other) {
    return this.amount === other.amount && this.currency === other.currency;
  }
}
const money = (amount, currency) => new Money(amount, currency);
// ▶ 重跑 → 仍綠。重構不改變行為,只改善結構。

重構這一步靠的正是 Extract Function 這類手法,而測試就是讓你敢動手的那張網。

IMPORTANT

三個階段的紀律:紅燈時只准寫剛好讓它失敗的測試、綠燈時只准寫剛好讓它通過的程式碼、重構時不准改變行為(測試必須全程保持綠)。 三者不混做,就像 分離命令與查詢 一樣——一次只做一件事。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading