Red-Green-Refactor
是什麼?
Red-Green-Refactor 是 TDD(Test-Driven Development)的核心三步驟循環。每一輪只處理一個小功能,確保程式碼始終在可控的狀態下成長。
ℹ️TDD 的心臟
Red-Green-Refactor 不只是一個技巧,它是 TDD 的基本節奏。所有 TDD 實踐都建立在這個循環之上。
核心觀念
- Red(紅燈):先寫一個會失敗的測試,明確定義你想要的行為。這個測試必須因為「功能尚未實作」而失敗,不是因為語法錯誤或編譯失敗。如果測試一開始就是綠燈,代表測試寫得不對或功能已經存在
- Green(綠燈):用最少的程式碼讓測試通過,不多不少。「最少」的意思是:可以回傳硬編碼值(hard-coded value),可以只處理當前測試案例,不要提前泛化
- Refactor(重構):在測試保護下改善程式碼品質,消除重複和壞味道。重構(Refactoring)是指「在不改變外部行為的前提下改善程式碼的內部結構」,典型操作包含 Extract Method、Rename Variable、Remove Duplication
- Baby Steps:每次循環只處理一個小增量,降低風險。一個 Baby Step 的大小標準:你有信心在 5 分鐘內完成 Red-Green-Refactor 的完整循環。如果超過 10 分鐘還沒綠燈,代表步子太大了
- 測試即規格:測試不只是驗證工具,它是你的活文件(Living Documentation)。Living Documentation 是指隨程式碼一起更新、永遠反映系統當前行為的文件,測試就是最好的例子
常見誤區
⚠️新手常踩的坑
- 在 Red 階段寫太多測試:一次只寫一個測試,不要一口氣寫十個
- 在 Green 階段過度設計:只寫剛好讓測試通過的程式碼,抵抗「順便」的衝動
- 跳過 Refactor:綠燈後急著寫下一個測試,讓技術債悄悄累積
- Refactor 時改了行為:重構只改結構,不改功能——測試必須持續綠燈
流程
Red — 寫失敗測試
撰寫一個描述預期行為的測試,執行後確認它失敗(紅燈)
Green — 讓測試通過
用最簡單、最少的程式碼讓測試通過(綠燈)
Refactor — 重構
在所有測試通過的保護下,改善程式碼結構與可讀性
重複循環
回到 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)
// 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)
// 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)
# 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 → 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 | 每次循環只處理一個小增量 | | 核心好處 | 持續獲得信心、降低風險、程式碼品質有保障 | | 代價 | 初期速度較慢,需要紀律來維持循環節奏 |