驗收條件有很多種寫法,但因為 AI 產出的速度遠快過人 review 的速度,因此只有能自動驗證的那一部分,才有可能跟上 AI 的產出速度。
昨天把工作單元切好了。
在真正開始開發之前,我想先處理一件事:規格裡寫了幾十條「系統應該怎樣」,其中有多少是真的可以被機器驗證的?
如果最後還是得靠人一條一條看,AI 產得再快,review 都會變成瓶頸。
| 誰來驗 | 被違反的時候 | |
|---|---|---|
| 描述性驗收條件 | 人 | 沒有任何事發生 |
| 可執行的檢查 | 機器 | 報錯 |
/speckit-specify 會順手產一份規格品質清單,拿它當例子:
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Edge cases are identified
這份清單當然有它的價值,它檢查的是規格本身的品質。
真正重要的是分清楚:哪些需要人判斷,哪些可以交給機器驗證。
AI 可以一次產出大量規格、程式碼和測試,但 review 的速度不會因此等比例增加,人還是得一行一行讀、一個情境一個情境確認。
所以在整條流程裡,能自動驗證的部分,是唯一不需要增加人工 review 成本的驗收方式。
如果只把規格交給 AI,「做完了沒」最後還是由 AI 自己判斷,它會用自己的理解補完沒有寫清楚的地方,然後直接告訴你完成了。
但如果先把規格轉成測試,「完成」的判定就不再只存在 AI 的回答裡。
測試通過,就是目前定義下的完成,測試沒通過,就是還沒完成,讓完成的判準變成可執行的東西。
這是我實際跑過一輪後最有感的地方,把規格交給 AI,拿回來的通常不是他執行的過程:
寫測試
↓
測試失敗
↓
寫實作
↓
測試通過
而是執行完的結果:models / services / api / tests 一次全出來。
這會讓傳統 TDD 裡很重要的「紅燈階段」消失,因為 AI 很可能是對著自己剛寫完的實作,再寫出一套測試。
結果就是:測試全綠,但沒有人證明過它真的能抓到錯誤。
所以現在我會要求:先看到紅燈,再相信綠燈,紅燈至少證明了這條測試真的有能力因為某個規則被違反而失敗。
這個專案裡,有三種規則適合直接轉成測試。
例如:已送出的版本不可修改。
其中一種最直接的保護方式,就是根本沒有修改版本的 API,所以測試可以直接檢查路由:
assert version_routes == {
"/api/versions/{version_id}": ["GET"],
"/api/versions/{version_id}/confirm": ["POST"],
"/api/versions/{version_id}/reject": ["POST"],
}
如果之後有人,或某個 AI session,偷偷加了一支 PATCH,測試會直接紅。
文件裡寫:請不要新增修改版本的 endpoint,不會擋住任何人。
資料庫裡有兩支 trigger 在守唯讀規則,問題是 alembic autogenerate 不會替我產生 trigger,也不會告訴我 trigger 不見了,所以不能只測「現在資料不能被修改」,要直接查資料庫系統表,確認這兩支 trigger 還存在。
因為 migration 有可能成功執行,但真正重要的東西根本沒有被建立。
還有一種規則,文件很容易寫,但很難只靠一般單元測試表達,例如:最後一個人確認的同時,另一個人退回。兩個請求如果同時讀到「待確認」,各自往下執行,就可能產生競態條件。
要驗證這件事,得真的讓兩個操作同時發生,再確認最後的狀態符不符合規則。這種情境文件描述得出來,但只有測試能反覆驗證它沒有真的發生。
所以從這個工作單元開始,我也把開發流程寫進 CLAUDE.md:
每個工作單元走 RED → GREEN → IMPROVE,至少兩筆 commit:
`test:` 在前(測試紅),`feat:` 在後(測試綠)。
這裡我特別要求兩筆 commit,它讓:「有沒有先寫測試?」這個問題,從一個需要相信開發者的問題,變成可以從 git log 查到的紀錄。這跟前面幾天得到的結論其實是一樣的:
規則不能只寫成「請遵守」,還要寫成之後可以檢查的形式。
拿「確認與退回」這個工作單元實際跑一輪,我先把四條驗收條件轉成測試,一共六個測試案例,第一次執行全部紅燈,是因為功能還沒完成。
接著開始實作,測試一條一條變綠,中間也發現一個 UI 結構問題:確認進度原本只是散落在一段文字裡,測試很難準確驗證。把它拆成獨立元素後,不只測試比較容易寫,UI 結構也更清楚。
最後在 IMPROVE 階段,又發現一個原本驗收條件沒有涵蓋的問題:作者看得到「正在等待確認」,但真正需要做決定的確認人,打開畫面後卻什麼都看不到。
這個 Bug 很值得記下來,即使測試全綠,不代表系統沒有問題,只能確定目前定義出來的驗收條件都通過了。測試的價值不只是「抓 Bug」,更重要的是,在實作之前先規範出到底什麼叫做完成?
實際跑完第一個工作單元後,我留下三個原則。
如果測試從來沒有失敗過,我其實不知道它能不能抓到問題。所以先紅,再綠,不是為了遵守 TDD 的儀式,而是先確認測試真的有能力驗證這條規則。
規格裡寫:不在確認名單的人不能確認,測試則把它變成:如果真的出現這個情境,測試必須失敗。
這兩者最大的差別是:前者需要有人記得;後者會自己出聲。
開發流程變成:
規格
↓
找出可以自動驗證的規則
↓
先寫測試
↓
RED
↓
AI 實作
↓
GREEN
↓
IMPROVE
↓
人工確認「有沒有漏掉沒被測試描述的事情」
測試負責守住已經定義出來的規則,而人工 review,則負責繼續找:
是不是還有我們根本沒想到的規則?
明天:後端骨架,一條 API 走完分層。