Red-Green-Refactor

是什麼?

Red-Green-Refactor 是 TDD(Test-Driven Development)的核心三步驟循環。每一輪只處理一個小功能,確保程式碼始終在可控的狀態下成長。

ℹ️TDD 的心臟

Red-Green-Refactor 不只是一個技巧,它是 TDD 的基本節奏。所有 TDD 實踐都建立在這個循環之上。

核心觀念

常見誤區

⚠️新手常踩的坑

  • 在 Red 階段寫太多測試:一次只寫一個測試,不要一口氣寫十個
  • 在 Green 階段過度設計:只寫剛好讓測試通過的程式碼,抵抗「順便」的衝動
  • 跳過 Refactor:綠燈後急著寫下一個測試,讓技術債悄悄累積
  • Refactor 時改了行為:重構只改結構,不改功能——測試必須持續綠燈

流程

1

Red — 寫失敗測試

撰寫一個描述預期行為的測試,執行後確認它失敗(紅燈)

2

Green — 讓測試通過

用最簡單、最少的程式碼讓測試通過(綠燈)

3

Refactor — 重構

在所有測試通過的保護下,改善程式碼結構與可讀性

4

重複循環

回到 Red 階段,為下一個小功能寫新測試

上圖展示了 TDD 的核心節奏:每一輪循環只處理一個小增量。Red 階段定義目標、Green 階段最小實現、Refactor 階段改善品質,然後進入下一輪。這個循環通常在 2-10 分鐘內完成一次。

測試框架選擇

| 語言 | 推薦框架 | 選擇理由 | |------|----------|----------| | C# | xUnit | .NET 社群主流,與 ASP.NET Core 深度整合。NUnit 也是成熟選擇,差異在:xUnit 每個測試方法建一個新實例(天然隔離),NUnit 共用實例(需手動管理狀態) | | TypeScript | Vitest | Vite 生態首選,原生支援 ESM、TypeScript 零配置。Jest 仍是最大社群,但配置 TypeScript 較繁瑣。選擇標準:用 Vite 建構就選 Vitest,用 Webpack/CRA 就選 Jest | | Python | pytest | 語法簡潔(用 assert 而非 self.assertEqual),plugin 生態豐富。unittest 是標準庫但較冗長 |

程式碼範例

C#(xUnit)

csharp
// Red: 先寫失敗的測試
public class StringCalculatorTests
{
    [Fact]
    public void Add_EmptyString_ReturnsZero()
    {
        var calculator = new StringCalculator();
        var result = calculator.Add("");
        Assert.Equal(0, result);
    }
 
    [Fact]
    public void Add_SingleNumber_ReturnsThatNumber()
    {
        var calculator = new StringCalculator();
        var result = calculator.Add("5");
        Assert.Equal(5, result);
    }
 
    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
    {
        var calculator = new StringCalculator();
        var result = calculator.Add("1,2");
        Assert.Equal(3, result);
    }
}
 
// Green: 最少程式碼讓測試通過
public class StringCalculator
{
    public int Add(string numbers)
    {
        if (string.IsNullOrEmpty(numbers)) return 0;
        return numbers.Split(',').Sum(int.Parse);
    }
}

TypeScript(Jest)

typescript
// Red: 先寫失敗的測試
describe("StringCalculator", () => {
  it("returns 0 for empty string", () => {
    const calculator = new StringCalculator();
    expect(calculator.add("")).toBe(0);
  });
 
  it("returns the number for a single number", () => {
    const calculator = new StringCalculator();
    expect(calculator.add("5")).toBe(5);
  });
 
  it("returns sum for two numbers", () => {
    const calculator = new StringCalculator();
    expect(calculator.add("1,2")).toBe(3);
  });
});
 
// Green: 最少程式碼讓測試通過
class StringCalculator {
  add(numbers: string): number {
    if (numbers === "") return 0;
    return numbers.split(",").reduce((sum, n) => sum + parseInt(n), 0);
  }
}

Python(pytest)

python
# Red: 先寫失敗的測試
from string_calculator import StringCalculator
 
def test_add_empty_string_returns_zero():
    calc = StringCalculator()
    assert calc.add("") == 0
 
def test_add_single_number_returns_that_number():
    calc = StringCalculator()
    assert calc.add("5") == 5
 
def test_add_two_numbers_returns_sum():
    calc = StringCalculator()
    assert calc.add("1,2") == 3
 
# Green: 最少程式碼讓測試通過
class StringCalculator:
    def add(self, numbers: str) -> int:
        if not numbers:
            return 0
        return sum(int(n) for n in numbers.split(","))

概念圖

Red (寫失敗測試)
寫最少程式碼
Green (讓測試通過)
改善結構
Refactor (重構)
下一個測試
Baby Steps
每次只加一點
Living Documentation

此圖呈現 Red-Green-Refactor 的循環關係:三個核心階段形成一個永不停止的迴圈,而 Baby Steps 和 Living Documentation 是支撐這個循環的兩大原則。注意箭頭方向:永遠是 Red → Green → Refactor → Red,不要跳過任何一步。

實戰補充

💡資深開發者的經驗

  • Green 階段可以「作弊」:回傳硬編碼值讓測試通過是完全合法的。Kent Beck 稱之為「Fake It Till You Make It」,後續測試會逼你寫出真正的邏輯
  • Refactor 階段是重點:新手常把重心放在 Red-Green,但真正的程式碼品質來自 Refactor。每次綠燈後花 30 秒問三個問題:(1) 有沒有重複的程式碼?(2) 變數和方法的命名清楚嗎?(3) 有沒有 Magic Number 可以抽成常數?
  • 用 Git 搭配 TDD:每次循環完成(綠燈 + 重構完)就 commit 一次,這樣隨時可以回到上一個穩定狀態。具體做法:git add -A && git commit -m "green: add X functionality"
  • 常見的循環時間:經驗法則是每個 Red-Green-Refactor 循環控制在 2-10 分鐘。如果超過 15 分鐘還沒綠燈,用 git stash 回到上一個穩定點,然後拆成更小的步驟重來

面試常見問題

Q:TDD 會不會讓開發速度變慢?

A:短期會慢 15-30%(因為要先寫測試),但中長期會加快,因為:(1) Bug 在寫入時就被發現,不需要事後 Debug;(2) 重構有安全網,改程式碼更有信心;(3) 測試就是文件,新人上手更快。研究數據(Microsoft、IBM 的案例)顯示,TDD 專案的 Bug 密度降低 40-90%,整體交付時間相近或更短。

Q:什麼情況下不適合用 TDD?

A:(1) 探索性原型(Spike)——你還不知道 API 長什麼樣,先寫 spike 探索,確定方向後再用 TDD 重寫;(2) 純 UI 排版——視覺呈現難以用斷言驗證,改用 Visual Regression Testing;(3) 一次性腳本——只跑一次的資料遷移腳本,測試的 ROI 太低。

理解測驗

🤔 在 Red-Green-Refactor 中,Red 階段應該做什麼?

🤔 Green 階段的原則是什麼?

🤔 為什麼 Refactor 階段很重要?

重點整理

💡一句話記住

口訣:「紅問綠答白整理」—— 紅燈問問題、綠燈給答案、重構做整理,循環不停止。

| 概念 | 說明 | |------|------| | Red(紅燈) | 寫一個會失敗的測試,定義預期行為 | | Green(綠燈) | 用最少程式碼讓測試通過 | | Refactor(重構) | 在測試保護下改善結構,不改行為 | | Baby Steps | 每次循環只處理一個小增量 | | 核心好處 | 持續獲得信心、降低風險、程式碼品質有保障 | | 代價 | 初期速度較慢,需要紀律來維持循環節奏 |

你可能也想看

Test Doubles

按 ← → 鍵切換課程