測試驅動開發
輸出語言
預設使用繁體中文與使用者溝通,並以繁體中文撰寫測試策略、測試說明和實作總結。使用者明確指定其他語言或既有文件語言另有約定時,遵從該約定;程式碼、命令、產品名和必須保持原文的引用不強行翻譯。
哲學
核心原則:測試應透過公開介面驗證行為,而非實作細節。程式碼可以完全改變,但測試不應受影響。
好的測試是整合風格的:它們透過公開 API 執行真實的程式碼路徑。它們描述系統做什麼,而非如何做。一個好的測試讀起來像一份規格說明——「使用者可以用有效的購物車結帳」清楚地告訴你存在什麼功能。這類測試能夠在重構中存活,因為它們不在乎內部結構。
壞的測試與實作耦合。它們模擬內部協作者、測試私有方法,或透過外部手段驗證(例如直接查詢資料庫而非使用介面)。警訊:當你重構但行為未改變時,測試卻失敗了。如果你重新命名一個內部函式且測試失敗,那些測試是在測試實作,而非行為。
參見 tests.md 取得範例,以及 mocking.md 取得模擬指南。
反模式:水平切片
請勿先撰寫所有測試,再實作所有程式碼。這稱為「水平切片」——將 RED 階段視為「撰寫所有測試」,GREEN 階段視為「撰寫所有程式碼」。
這會產生糟糕的測試:
- 批次撰寫的測試測試的是想像中的行為,而非實際的行為
- 你最終測試的是事物的形狀(資料結構、函式簽名),而非使用者面對的行為
- 測試對真實的變化變得遲鈍——行為失效時測試通過,行為正常時測試失敗
- 你跑得比車燈還快,在理解實作之前就承諾了測試結構
正確做法:透過追蹤子彈進行垂直切片。一個測試 → 一個實作 → 重複。每個測試都回應你從上一個循環中學到的東西。因為你剛剛寫了程式碼,所以你確切知道哪些行為重要以及如何驗證它。
錯誤(水平):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
正確(垂直):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
工作流程
1. 規劃
探索程式碼庫時,請先閱讀 CONTEXT.md(如果存在),以便測試名稱和介面詞彙與專案的領域語言一致,並尊重你觸及區域中的 ADR。
在撰寫任何程式碼之前:
- 與使用者確認需要哪些介面變更
- 與使用者確認要測試哪些行為(優先排序)
- 當介面邊界不明確時,利用程式碼庫設計原則識別深度模組的機會。除非使用者明確要求,否則不要自動呼叫其他技能。
- 列出要測試的行為(而非實作步驟)
- 取得使用者對計畫的批准
提問:「公開介面應該長什麼樣子?哪些行為最重要需要測試?」
你無法測試所有事情。 與使用者確切確認哪些行為最重要。將測試工作集中在關鍵路徑和複雜邏輯上,而非每個可能的邊緣案例。
2. 追蹤子彈
撰寫一個測試,確認系統的某一個方面:
RED: 為第一個行為撰寫測試 → 測試失敗
GREEN: 撰寫最簡程式碼使其通過 → 測試通過
這就是你的追蹤子彈——證明路徑能端到端運作。
3. 增量迴圈
針對剩餘的每個行為:
RED: 撰寫下一個測試 → 失敗
GREEN: 最簡程式碼使其通過 → 通過
規則:
- 一次只處理一個測試
- 只撰寫足夠通過當前測試的程式碼
- 不要預測未來的測試
- 讓測試專注於可觀察的行為
4. 重構
所有測試通過後,尋找重構候選項目:
- 提取重複程式碼
- 深化模組(將複雜性移至簡單介面之後)
- 在自然的地方應用 SOLID 原則
- 考慮新程式碼揭示了既有程式碼的哪些資訊
- 每次重構步驟後執行測試
在 RED 狀態時切勿重構。 先達到 GREEN。
每個循環的檢查清單
[ ] 測試描述行為,而非實作
[ ] 測試僅使用公開介面
[ ] 測試在內部重構後仍能存活
[ ] 針對此測試的程式碼是最簡的
[ ] 未添加投機的功能