有一件事很多人用了很久 AI 才發現:叫它「寫完功能之後補上測試」,它補的測試常常是假的。
不是它故意。假設它把「滿千折百」寫錯成折兩百,你叫它補測試,它會看著自己的程式碼寫:「金額 1000 時,預期結果 800」。測試綠燈,順利通過,因為預期值是照它自己的錯誤邏輯算出來的。你以為有測試保護,其實是它自己出題自己答。
Matt Pocock 的 tdd Skill 把這種測試叫 tautological(套套邏輯):斷言用跟程式碼一樣的方式重算預期值,所以構造上一定通過,永遠不可能跟程式碼意見不同。他的規則是:預期值必須來自獨立的真相來源,已知正確的常數、一個手算的例子、規格書上的數字。
避免它的方法有一百年歷史,叫 TDD(測試驅動開發)。核心只有一個順序:
TDD 防作弊循環
先證明測試會失敗。
再讓 AI 寫到測試通過。
檢查壞味道與作弊行為。
在測試保護下整理。
紅燈是證據,它證明測試真的在監督
為什麼一定要先看到失敗?因為紅燈是唯一能證明「這個測試有在檢查東西」的證據。一個從來沒紅過的測試,你不知道它是真的在驗證,還是根本什麼都不檢查。先紅後綠這個順序,逼 AI 的預期值必須在程式存在之前就定下來,它沒有東西可以「照抄」。
Pocock 的 tdd Skill 把迴圈規則寫成三條,我照翻:紅在綠之前,先寫失敗的測試,再寫剛好夠通過的程式,不要預先做未來的功能;一次一片,一個介面、一個測試、一個最小實作;重構不在迴圈裡,那是審查階段的事。
還有兩個他特別點名的反模式。耦合實作:測試去 mock(假造)內部的協作者、測私有方法、或繞過介面直接查資料庫,特徵是你重構之後行為沒變,測試卻壞了。橫向切片:先把所有測試寫完、再把所有實作寫完,這樣寫出來的測試驗的是「想像中的行為」,對真實變動不敏感。他要求的是縱向:一個測試、一個實作、再下一個。
審查要用乾淨的眼睛,而且要有清單
程式寫完、測試通過之後,Pocock 的流程接一個 code-review。有兩個設計值得學。
用新的對話審。審查在獨立的子代理裡跑,不在寫程式的那場對話裡。理由是寫的那一方腦子裡還留著自己的推理,看自己的東西永遠覺得對,乾淨的眼睛才看得到問題。
審兩個軸,各有清單。一軸叫 Standards:程式有沒有照這個專案寫下來的規範。另一軸叫 Spec:程式有沒有做到原本 issue 或規格要求的事,缺了什麼、多做了什麼、看起來做了但做錯的。兩軸平行跑,結果並列,不合併排序。
Standards 那一軸有一份固定的基準清單:Fowler《重構》第三章的 12 種程式碼壞味道,每一種寫成「是什麼 → 怎麼修」:
| 壞味道 | 是什麼 | 怎麼修 |
|---|---|---|
| Mysterious Name | 名字看不出在做什麼 | 改名;改不出誠實的名字代表設計不清 |
| Duplicated Code | 同樣的邏輯出現在多處 | 抽出共用的,兩邊都呼叫它 |
| Feature Envy | 方法一直去碰別的物件的資料 | 把方法搬到它羨慕的那份資料旁 |
| Data Clumps | 同幾個欄位總是一起出現 | 包成一個型別,傳那個型別 |
| Primitive Obsession | 用字串或數字代替一個領域概念 | 給概念一個自己的小型別 |
| Repeated Switches | 同一種 switch/if 串在多處重複 | 改成多型或共用一張對照表 |
| Shotgun Surgery | 改一件事要動很多檔案 | 把一起變的東西收進同一個模組 |
| Divergent Change | 一個檔案因為好幾個不相干的理由被改 | 拆開,讓每個模組只因一種理由改 |
| Speculative Generality | 為了規格沒要求的需求加抽象 | 刪掉,等真的需要再說 |
| Message Chains | 一長串 a.b().c().d() | 在第一個物件上藏一個方法 |
| Middle Man | 一個類別幾乎只是轉發 | 砍掉,直接呼叫真正的目標 |
| Refused Bequest | 子類別忽略大部分繼承來的東西 | 放棄繼承,改用組合 |
兩條使用規則:專案自己寫的規範永遠優先,規範明說可以的就不算壞味道;每一項都是判斷,不是硬性違規,報告會寫「可能有 Feature Envy」,不是「違規」。工具已經在管的,跳過。
動手做
任何有執行權限的代理工具,把紅綠規則貼在任務說明裡就行:
這個功能用 TDD 做,規則: 1. 先寫測試。預期值要來自我給的規格或例子,不准照程式邏輯推算。寫完把測試貼給我看。 2. 跑測試,必須失敗(紅燈)。把失敗訊息貼給我。沒有紅燈不准往下。 3. 寫剛好夠通過這個測試的程式,不多做。跑到綠燈。 4. 一次只做一個測試,做完再下一個。不准先把所有測試寫完。 5. 過程中不准修改測試的預期值。想改,先停下來告訴我為什麼。 規格:(貼上,含具體例子:輸入 X → 應得 Y)
用 Claude Code 或 Codex、想直接裝的,npx skills@latest add mattpocock/skills 之後,tdd 是模型自動觸發的技能,你說「用 TDD 做」或提到 red-green-refactor 它就會用;寫完打 /code-review <起點>(例如 /code-review main)跑雙軸審查。第一次要先 /setup-matt-pocock-skills 設定 issue tracker。
網頁版做不到跑測試,但做得到「預期值獨立」:交辦時先給它三組你自己算好的「輸入 → 輸出」,要求它的程式對這三組都對,不要讓它自己編例子。
做對了的樣子:每個測試你都看過它紅過一次;預期值旁邊寫得出來源(規格第幾條、哪個手算例子);審查是在另一個對話或子代理裡做的;審查報告用的是「可能有…」,不是「違規」。
找一個 AI 幫你寫過測試的功能,看那個測試的預期值是從哪來的。如果是「看起來像照程式算出來的」,那就是套套邏輯,換成你手算的數字,再跑一次。
先寫功能再補測試,AI 會寫出照錯誤邏輯也會過的假測試。紅燈是證據,沒紅過的測試不算數。預期值必須來自程式以外的地方:規格、手算、已知正確的數字。審查用乾淨的眼睛、拿著清單,而且每一項是判斷,不是判決。
站內延伸
- 不准 AI 改測試的邊界 → 迴圈的停損
- Pocock 整套流程的起點 → 動工前先被拷問
- 測試清單的完整寫法 → 找方法AI Agent 測試
來源: mattpocock/skills:tdd/SKILL.md(tautological、implementation-coupled、horizontal slicing 三個反模式與迴圈三規則);mattpocock/skills:code-review/SKILL.md(雙軸、平行子代理、12 種壞味道與使用規則)。12 種壞味道原出處是 Martin Fowler《Refactoring》第三章。
功能先寫完再補測試,
它會自己出題自己答
什麼時候看這張:它補的測試全綠,你卻說不出那個預期數字是從哪來的。
- 預期數字要來自規格或你手算,不准照程式推回來
- 沒失敗過的測試,你不知道它到底有沒有在檢查
- 審查換一場乾淨的對話,而且要拿著清單
第一個動作找一個它幫你寫過測試的功能,把預期數字換成你自己手算的再跑一次。過不了就是抓到假的了。