測試驅動開發
TDD 是紅 → 綠循環。此技能是讓該循環產生值得保留的測試的參考:什麼是好的測試、測試該放在哪裡、反模式,以及循環的規則。每個章節在每個循環中都適用:請在循環之前和之中查閱它們,而不是之後。
在探索程式碼庫時,請先閱讀 CONTEXT.md(如果存在),以使測試名稱和介面詞彙與專案領域語言相符,並尊重你所觸及區域中的 ADR。
什麼是好的測試
測試透過公開介面驗證行為,而不是實作細節。程式碼可以完全改變;測試不該跟著變。好的測試讀起來就像一份規格:「使用者可以使用有效購物車結帳」精確告訴你存在什麼功能,而且它能在重構後存活,因為它不在乎內部結構。
參見 tests.md 了解範例,以及 mocking.md 了解模擬指南。
接合處:測試該放在哪裡
接合處是你進行測試的公開邊界:你觀察行為而不觸及內部的介面。測試位於接合處,絕不針對內部。
僅在預先同意的接合處進行測試。 在撰寫任何測試之前,請先列出待測試的接合處並與使用者確認。未經確認的接合處不得撰寫測試。你無法測試所有東西,因此事先同意接合處能讓測試精力落在關鍵路徑和複雜邏輯上,而不是每個邊緣案例。
提問:「公開介面是什麼?我們應該測試哪些接合處?」
當介面的形狀本身有爭議時(模組該多深、接合處該在哪裡、介面該暴露什麼),請呼叫 Skill 工具並帶入「codebase-design」取得詞彙。這是共享的來源,定義了模組、介面、深度、接合處、適配器、槓桿和局部性等術語,是一個供參考的文件,而非一個需執行的 session。
反模式
- 與實作耦合:模擬內部協作者、測試私有方法,或透過側通道驗證(查詢資料庫而非使用介面)。跡象:重構時測試失敗,但行為沒有改變。
- 同義反覆:斷言重新計算預期值的方式與程式碼相同(
expect(add(a, b)).toBe(a + b)、以相同方式手動推導的快照、與自身比較的常數),因此它無論如何都會通過,永遠無法與程式碼相矛盾。預期值必須來自獨立的真相來源:已知良好的字面值、運算範例、規格。 - 水平切片:先寫所有測試,再寫所有實作。大量測試驗證的是_想像的_行為:你測試的是事物的_形狀_而非使用者面對的行為,測試對真實變化不敏感,並且在理解實作之前就承諾了測試結構。應該改為垂直切片:一個測試 → 一個實作 → 重複,每個測試都是一枚追蹤子彈,回應上一個循環教給你的事。
循環的規則
- 紅先於綠。 先寫失敗的測試,然後只寫剛好能通過它的程式碼。不要預測未來的測試或加入推測性的功能。
- 一次一個切片。 每個循環一個接合處、一個測試、一個最小實作。
- 重構不屬於循環的一部分。 它屬於程式碼審查階段(參見
matt-code-review技能),而非紅 → 綠實作循環。