Theme / v1.2.3

Matt Pocock's Engineering Skills

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

實戰範例

實戰範例 005:用 /tdd 走一遍 Red-Green-Refactor 切片

新增電子郵件驗證規則時,先與 agent 確認 seam,再以獨立預期值驅動 red → green 迴圈。

情境背景

註冊流程要加一條新規則:使用者信箱必須是公司網域(@example.com)才能完成註冊。你想用測試驅動開發(Test-Driven Development)的方式加這個行為,避免之後改規則時沒有測試保護。

/tdd 的核心是 red → green 迴圈,refactor 階段已經移交給 /code-review。重點不是「寫很多測試」,而是「一次一個垂直切片,每片都有真實失敗的 red 與最小通過的 green」。


第 1 步:與 agent 確認 seam

目標:在寫任何測試前,先決定測試要打哪一層

AI 對話

  • You:

    /tdd
    我想加「信箱必須是 @example.com 網域才能註冊」這個行為。
  • AI:

    先確認 seam。這個驗證規則的測試應該打哪一層?

    我的提案:純邏輯函式 isAllowedEmail(email),輸入字串、輸出布林,不碰 DB 也不碰 UI。seam 是這個函式的輸入輸出。同意嗎?

  • You:

    同意,先以純函式為 seam。註冊流程之後再接。

確認 seam 是 /tdd 的鐵律。沒有這一步,agent 不會往下走。


第 2 步:Red,第一個切片

目標:寫一個會失敗的測試,描述接下來要加的行為

AI 對話

  • AI:

    Red

    test('公司網域信箱通過', () => {
      expect(isAllowedEmail('[email protected]')).toBe(true);
    });

    預期值 true 來自獨立判斷(你告訴我的規則),不是重跑程式碼。跑一下確認它因為對的原因失敗。

  • You:

    失敗了。錯誤訊息是 isAllowedEmail is not defined

  • AI:

    這是正確的 red。函式還不存在,符合預期。


第 3 步:Green,最小實作

目標:寫剛好讓測試通過的程式碼,不多寫

AI 對話

  • AI:

    Green

    export function isAllowedEmail(email: string): boolean {
      return email.endsWith('@example.com');
    }

    跑測試。

  • You:

    通過了。

注意 Green 階段不偷寫下一個切片。如果你想順手加「不允許外部網域」的判斷,那是下一個 red 的事。


第 4 步:Red,第二個切片

目標:為反向情境寫測試

AI 對話

  • AI:

    Red

    test('外部網域信箱不通過', () => {
      expect(isAllowedEmail('[email protected]')).toBe(false);
    });
  • You:

    失敗。實際回傳 true,預期 false

  • AI:

    等等,這代表現有實作有 bug。endsWith 不會區分大小寫,而且 [email protected] 這類前置攻擊也會通過。我們先修最小切片,再處理邊界。

  • AI:

    Green

    export function isAllowedEmail(email: string): boolean {
      return /^[^@]+@example\.com$/.test(email);
    }

    跑測試。

  • You:

    兩個切片都通過。


第 5 步:Red,第三個切片(大小寫)

目標:處理大小寫邊界

AI 對話

  • AI:

    Red

    test('大寫網域也應通過', () => {
      expect(isAllowedEmail('[email protected]')).toBe(true);
    });
  • You:

    失敗。實際 false,預期 true

  • AI:

    Green

    export function isAllowedEmail(email: string): boolean {
      return /^[^@]+@example\.com$/i.test(email);
    }

反模式提醒

同義反覆測試(tautological test)

預期值用同一個函式再算一遍,這種測試不可能失敗,給零信心。預期值必須來自獨立來源,例如規則文件、手算、或已知正確的參考實作。

橫向鋪層而非垂直切片

把整個資料層寫完、再寫 service、再寫 UI,中間沒有任何可運作的切片。/tdd 要求每一圈都是端到端的細長功能,這樣才能在每個切片後看到測試回饋。

漂浮的 refactor

在 red → green 之間偷改結構。refactor 屬於 /code-review 階段,不在這裡。想重構就用 /code-review 收尾。


工具使用摘要

Skill 用途 在本例的作用
tdd red → green 迴圈 三個垂直切片,每片都有獨立預期值的 red 與最小 green
code-review refactor 收尾 tdd 結束後接手,檢查命名與重複

結果

  • 三個垂直切片都完成:合法網域、外部網域、大小寫邊界
  • 全程沒有漂浮的 refactor,refactor 留給後續 /code-review
  • seam 是純函式層,未來要接註冊流程時不會被測試綁死

關鍵學習點

  • 寫測試前先與 agent 確認 seam,是 /tdd 的鐵律。沒有 seam 共識就不開始。
  • 預期值必須來自獨立來源,不能用同一份邏輯再算一遍當 oracle。
  • Green 只為了讓紅燈轉綠,不偷寫下一個切片,也不趁機 refactor。
  • 三個反模式要記得:同義反覆測試、橫向鋪層、漂浮 refactor。