Theme / v1.2.3

Matt Pocock's Engineering Skills

可組合的 AI 工程工作流技能庫

指令詳解

/tdd 指令詳解

測試驅動開發——red → green 迴圈,一次一個垂直切片來建功能或修 bug

指令用途

/tdd 是測試驅動開發 skill。它鼓勵 red → green 迴圈(refactor 階段已移至 /code-review),一次以垂直切片 / tracer bullet 的方式建構功能或修復 bug。

核心迴圈由模型早已熟悉的 leading words 來錨定,所以這個 skill 是參考導向的 —— 它不一步一步重述迴圈,而是把最耐久的觀念(垂直切片、seam —— 測試只放在預先同意的接縫處)收進反模式與簡短的「迴圈規則」清單裡。

seam 是這個 skill 對「測試要寫在哪裡」的 leading word:只在預先同意的 seams 測試,寫任何測試前都先與使用者確認 seam 在哪。


運作流程(red → green)

  1. Red:先寫一個會失敗的測試。它描述你接下來要加的行為,一個切片的最小一步。
  2. Green:寫剛好足夠的程式碼讓測試通過 —— 不多不少。
  3. Refactor 不在這裡:refactor 屬於 /code-review 階段。)

每個迴圈都是一個垂直切片 —— 一條從 UI/API 端點一路打到資料層、可獨立運作並通過測試的細長功能。


迴圈規則

  • 一次一個切片:每個 red → green 處理一個垂直切片,而非橫向鋪一整層。
  • 只在 seams 測試:寫測試前,先與使用者確認 seam 在哪;不在非 seam 處加測試。
  • Red 必須真的 red:先看到測試失敗(而且失敗的原因是對的),才寫實作。
  • Green 只為了讓它過:不趁機重構、不超前寫下一個切片。
  • 預期值來自獨立的真實來源:不把「用同樣方式算一遍」當成 assertion(見下方反模式)。

實戰對話範例

範例一:新增折扣計算

  • You:

    我想加一個「滿千打九折」的折扣功能。

  • AI:

    先確認 seam。這個折扣邏輯的測試應該打哪一層?

    我的建議:純邏輯函式 applyDiscount(cartTotal), seam 是這個函式的輸入輸出,不碰 DB 也不碰 UI。同意嗎?

  • You:

    同意。

  • AI:

    Red

    test('滿千打九折', () => {
      expect(applyDiscount(1000)).toBe(900);
    });

    預期值 900 來自獨立計算(1000 × 0.9),不是重跑程式碼。跑一下確認它 因为對的原因 失敗。

  • You:

    失敗了,因為函式還不存在。

  • AI:

    Green:寫剛好讓它過的實作:

    export function applyDiscount(total: number): number {
      return total >= 1000 ? total * 0.9 : total;
    }

    跑測試,通了就進下一個切片(例如「不滿千原價」)。


反模式(重點)

同義反覆測試(tautological test)

測試的 assertion 用與實作相同的方式重新算一遍。這種測試因構造而通過,給零信心 —— 它與「實作耦合」反模式不同,是另一種毛病。

  • 錯(BAD)

    test('折扣(同義反覆)', () => {
      const inputs = [1000];
      const expected = inputs.map(applyDiscount); // 用同一個函式算
      expect(inputs.map(applyDiscount)).toEqual(expected);
    });

    這個測試不可能失敗,即使 applyDiscount 邏輯全錯

  • 對(GOOD):預期值來自獨立的真實來源(手算、仕樣表、已知正確的參考實作):

    test('折扣(獨立預期值)', () => {
      expect(applyDiscount(1000)).toBe(900); // 900 是手算的 ground truth
    });

原則:預期值必須來自獨立的真實來源,不能是把被測的程式碼重跑一遍。

橫向鋪層而非垂直切片

把整個資料層一次寫完、再寫整個 service 層、再寫 UI —— 中間沒有任何可運作的切片、沒有測試反饋。一次一個垂直切片才能讓每一圈都有 red → green 的反饋。

漂浮的 refactor

red → green 之間趁機大改結構。refactor 屬於 /code-review 階段。


何時應該使用

  • 建新功能:一次一個垂直切片。
  • 修 bug:先寫會失敗的測試重現 bug(與 /diagnosing-bugs 的建立反饋迴圈呼應),再 green。
  • 加行為:任何需要明確描述「這個行為應該是什麼」的地方。

何時不應該使用

  • 探索性原型:還不確定要做什麼 —— 用 /prototype
  • 純 refactor:沒有新行為要描述 —— 屬於 /code-review
  • 一次性腳本:不值得測試覆蓋。

與其他技能的關係

/tdd/implement 在預先同意的 seams 所驅動的 red-green 引擎,也是 /code-review 的上游(後者接手 refactor 階段)。它從 /codebase-design 取得介面設計指引。不確定該用哪個 skill 時,用 /ask-matt 來路由。