測試驅動開發(TDD)
先寫會失敗的測試。看著它失敗。寫最少的程式碼讓它通過。重構。 立即通過的測試證明不了什麼——你從未看到它抓到一個錯誤。
鐵律: 沒有生產程式碼,就沒有失敗的測試。如果你在測試之前就寫了程式碼,刪掉它並重新開始。「保留作為參考」不過是偽裝的後測——刪除就是刪除。
何時使用
- 實作任何新功能或行為
- 修復錯誤(先用測試重現它——「證明模式」)
- 重構或變更現有行為
- 觸發詞:"tdd"、"test-driven"、"red green refactor"、"測試驅動開發"、"紅綠重構"、"測試驅動"
不適用於: 為已寫好的程式碼產生測試(請使用 test-generation);一次性原型或純設定(直接實作即可)。
例外情況(需與使用者確認): 一次性原型、產生的程式碼、不影響行為的純設定變更。
步驟
0. 先找出技術棧
循環是通用的;指令則不是。在第一個測試之前,找出這個儲存庫的測試方式:閱讀 package.json / pyproject.toml / Cargo.toml / pom.xml / go.mod,檢查是否有 ./gradlew、Makefile、CI 工作流程以及現有的測試檔案命名方式。永遠不要假設 npm test——每次 RED、GREEN 和最終驗證都要使用儲存庫自己的指令。
1. RED——寫一個會失敗的測試
一個行為,一個測試,一個垂直切片(能回應上一次循環告訴你資訊的 tracer bullet)。寫出你希望存在的測試——它描述的是所需 API,而不是實作方式。以規格命名(「拒絕空白的 email」,而不是「test1」)。
在寫測試之前,先協商好接縫(seam)(被測試的公開介面)。未經確認的接縫不要測試——事先協商接縫能將測試工作集中在關鍵路徑上,而不是每個邊界情況。優先使用真實程式碼而非模擬(mock);如果必須替換相依性,請使用最簡單的替換方式(真實 > 假實作 > 樁 > 模擬——請參考 references/test-strategy.md)。模擬用來隔離邊界,而不是被測試的物件本身。
2. 驗證 RED——看著它失敗
執行聚焦的測試指令。確認:
- 它失敗了(不是錯誤——錯誤表示修正設定後重新執行)
- 它因為正確的原因失敗(功能缺失,而不是拼寫錯誤)
- 立即通過的測試表示你在測試既有行為——修正測試
3. GREEN——寫最少的程式碼
能讓測試通過的最簡單程式碼。沒有投機的功能,沒有「彈性」,沒有重構周邊程式碼。YAGNI(你不需要它)。不要在生產類別中加入僅供測試用的方法(destroy()、reset())——將清理程式碼放在測試工具中。
4. 驗證 GREEN——看著它通過
執行聚焦的測試指令,然後執行完整測試套件。確認:
- 新的測試通過
- 沒有其他測試被破壞(如果有,立即修正——不要跳過)
- 輸出是乾淨的(沒有警告,沒有跳過的測試)
5. 重構(REFACTOR)——清理
僅在綠燈狀態下:提取輔助函式、改善命名、移除重複。每次重構步驟後都執行測試。不要新增行為。重構屬於審查階段,而不是紅燈到綠燈的實作循環。
6. 提交(Commit)
將測試和實作一起作為一個垂直切片提交。然後開始下一個會失敗的測試。
錯誤修復——「證明模式」
不要從修復開始。先寫一個能重現錯誤的測試(RED 確認錯誤存在),然後修正(GREEN 證明修復有效)。該測試會留下來作為回歸防護。永遠不要在沒有重現測試的情況下修復錯誤。
驗證
- 每個新行為都有一個最初會失敗的測試
- 完整測試套件使用儲存庫自己的測試指令通過
- 錯誤修復包含一個在修復前會失敗的重現測試
- 測試名稱描述的是行為,而不是實作
- 沒有跳過或停用的測試;生產程式碼中沒有僅供測試用的方法
參考資料
- ${CLAUDE_PLUGIN_ROOT}/references/engineering-principles.md — 每個技能都共享的紀律
- references/test-strategy.md — 測試金字塔、測試替身層級(真實 > 假實作 > 樁 > 模擬)、DAMP 優先於 DRY
- references/testing-anti-patterns.md — 模擬誤用、僅供測試用的方法、部分模擬、閘道函式
- references/mocking.md — 何時使用模擬、設計可模擬性
- references/good-tests.md — 好測試與壞測試的範例、同義反覆測試