Test-driven 是錯的 — 為什麼我的 QA Agent 看 spec 不看 test
這是 QA Pipeline 系列的第 2 篇。前一篇 〈15 個 Agent 跑兩週的全景〉 把整條 pipeline 的結構畫出來了。這一篇要回答一個更根本的問題:agent 在比對什麼 vs 什麼?
業界絕大多數「自動修 bug 的 agent demo」答案是:test 跑失敗 → LLM 讀 test 錯誤 → 改 code 讓 test 過。這條路在 demo 影片裡很漂亮、在 production 會出大事。
一個「修成功了但其實修錯了」的案例
我們的 E2E 裡有一個 test 大概長這樣:
test("shows inline error when analytics endpoint returns 429", async ({ page }) => {
await page.locator("text=分析").or(page.locator("text=Analytics")).click()
await expect(page.getByRole("alert")).toBeVisible()
})
這個 test 一開始紅了。錯誤訊息是 Playwright 的 strict mode violation — .or() locator 解析出兩個 element,而不是一個。
傳統 agent 的反應:
「哦 strict mode 不讓你這樣寫。改成
.or().first()、或者 wrap 一層、或者關掉 strict mode。」
改完 test 過了、PR 開了、merge 了。
但原本的 test 意圖是測 429 rate limit 的 UI,而改法繞過了 strict mode、根本沒測到 429。這個 test 從「紅」變成「綠但沒效」。line coverage 還是滿分。
這是我們最早踩過的坑,Linear AUT-1。當時的 closed_reason 留了一句:「Promise.race replaces .or() chain — PR #52 was wrong approach」— PR #52 就是那個「把 test 改綠但沒測到東西」的錯誤 fix。後來重做是因為比對到 spec 才發現 test 改法跟 requirement 脫節。
test 也會說謊,但 spec 不會
這不是 agent 笨、是把 test 當 SSOT 的設計本身就有 bug。test 是人寫的、人會寫錯、人會為了讓 CI 綠而妥協。讓 LLM 讀 test 修 code,等於讓它把一個可能錯的假設當成真理。
所以我做了另一個選擇:SSOT 是 spec,不是 test。
三方比對:spec × source × test
qa-planner 每次 session 讀三樣東西:
- Spec(
docs/superpowers/specs/routes/、docs/superpowers/plans/、docs/plans/)— 真相 - Source code(
src/**)— 實際行為 - Test(
e2e/**、src/**/__tests__/**)— 驗證層
然後把三方不一致的情況分成 6 類:
| 三方狀態 | 分類 | fixer 動作 |
|---|---|---|
| spec ≠ source,test 跟 source 一致 | code-bug | 改 source |
| spec ≠ test,source 跟 spec 一致 | test-bug | 改 test |
| spec 有、source 有、test 沒有 | coverage-gap | explorer/fixer 寫新 test |
| spec 有要求、source 有實現、test 過但不穩 | flaky | fixer 找 root cause |
| source 跟 spec 有歧異、可能是效能退化 | perf-regression | 不修,丟回人 |
| spec 本身不清楚 | needs-spec-review | 不修,丟回人 |
最後兩類最重要。當 spec 不清楚、agent 直接停手。這是整個系統穩定的基石。
過去兩週 53 個真實 bug 裡:
- code-bug 12 個(23%)— 改 source
- test-bug 6 個(11%)— 改 test
- coverage-gap 27 個(51%)— 補 test
- flaky 3 個(6%)— 修穩定性
- needs-spec-review 5 個(9%)— 完全沒碰 code
那 5 個 needs-spec-review 就是「agent 判斷:我不確定 spec 要什麼,這個我不處理」的安全出口。這一層沒有、agent 就會開始瞎猜、然後把「猜錯的東西」寫進 PR。
實際的 qa-fixer 分類決策
這段直接是 .claude/agents/qa-fixer.md 的原文(經過少許格式整理):
## Scope
| Issue Type | What You Fix | Where |
|---------------|-------------------------------------|----------------------|
| code-bug | Source code to match spec | src/** |
| test-bug | Test code to match spec | e2e/**, src/**/__tests__/** |
| coverage-gap | Write new tests for untested spec | e2e/**, src/**/__tests__/** |
| flaky | Root cause in code OR test | src/** or e2e/** |
**Do NOT fix:** `perf-regression` or `needs-spec-review`
— these require human judgment.
注意最後一行。fixer 拿到 needs-spec-review issue 會直接跳過、把它 re-assign 成 "Awaiting Human Review" 留給人。這不是 fallback、是 primary safety property。
一個 needs-spec-review 真實案例
兩週資料裡最新的一筆是 AUT-61:api-types::verify-code-type-vs-usage-type。
前因:qa-analyzer 在跑 AUT-60 的時候發現,某個 API 的 TypeScript 型別裡有個 field 叫 type,但另一個呼叫方用的是 usage_type。兩邊各自 work、但互相牴觸。這是 code-bug 還是 spec-bug?analyzer 不知道。
如果是傳統 agent:LLM 會挑一邊改、寫 migration、發 PR、可能動到生產資料流。
我們的 agent 判斷:這不是 agent 該做的決策。Linear 新 issue 狀態直接 set 成 Awaiting Human Review、打 needs-spec-review + needs-human-review label、不開 PR。
隔天早上我 15 秒看完 Linear 知道要跟後端確認型別對應、找 PM 問業務語意、最後決定保留兩個欄位、寫 spec 補充。這個決策流程是對的,agent 的角色是把這件事推上人類的桌子、不是自己亂動。
有人會問:讓 LLM 生 spec 不行嗎
不行。理由是:
Spec 是需求,不是程式碼。需求來自產品、法規、使用者研究,LLM 沒有這些 context。讓 LLM 生 spec 等於讓它自己出題自己改 — 你會得到一個完全自洽但跟市場脫節的系統。
我的 pipeline 不自動產 spec。spec 只有人寫、agent 只比對。這個職責分界不能鬆。
Spec 放哪裡 — 最低成本的做法
很多團隊 spec 散落在 Notion、Figma、Slack、PM 腦袋裡。agent 讀不到、人讀一讀也記不住。
我做的事簡單:spec 寫成 markdown、丟進 repo。具體路徑:
docs/superpowers/specs/routes/— 按 route 整理的結構化 spec(acceptance criteria、邊界條件、錯誤 UI)docs/superpowers/plans/— SDD(Software Design Document)級別的模組設計docs/plans/— 功能級別的 plan 文件
每個 qa-planner prompt 裡這三個路徑是 hardcoded 的。planner 開場第一步就是 Glob 這三個目錄撈所有 .md。
這個決定有幾個副作用比原本 spec 文件化本身更有價值:
- Spec review 跟 code review 一起做 — PR 改到 spec 的 requirement,reviewer 馬上看到
- Spec diff 可追溯 — 什麼時候加的需求、git blame 查得到
- PM 也 commit 進 repo — 業務邏輯變更有 PR trail
反對意見清單
幾個我被問過的質疑:
「如果 spec 本身就錯呢?」
這就是 needs-spec-review 存在的原因。agent 偵測到 spec 矛盾或歧義、不猜、丟回人。spec 錯不會自動傳到 code。
「我沒時間寫 spec」 那你其實也沒時間寫好 test。test-driven 團隊的 test 品質 = 寫 test 投入的時間,這條路你要嘛面對、要嘛品質被迫下降。spec-driven 把「寫 requirement」這件事上移一層、讓 test 從 SSOT 降級成 assertion layer,負擔反而輕。
「LangGraph / CrewAI / Claude Agent SDK 哪個比較好做這個?」 不是框架問題,是哲學問題。任何框架都能做、只要你把 SSOT 換成 spec。這個變更是一行 prompt(「planner 讀 spec 不讀 test」),框架選什麼無所謂。
Takeaway
用最短的話講完整篇:
Test 是假設的驗證。Spec 是假設本身。當它們不一致的時候,不要改驗證、要回去看假設。
下一篇 #3 講我系統裡最有趣的一層 — 跨 session memory、讓 agent 從 fix 裡抽 pattern、再用這個 pattern 回頭掃整個 repo。那是把 spec-driven 從 linear 變指數的關鍵。
如果你團隊正在考慮導入 QA agent、或者已經在踩「LLM 修 test 但越修越糊」的坑,Twitter @waiting7777 聊聊。
Waiting7777
WoW Arena 冠軍轉前端,用電競 meta 思維拆解技術趨勢。
相關文章
AI Agents開發者工具+1Orca 七個月拿下 10 萬顆星:我拆了它的 GitHub 數據,看到 agent 開發真正的瓶頸
一個由 agent 寫出來、拿來管 agent 的開源 IDE。19,000 個 commit、979 次 release 背後,最值得看的數字是 2,525 個放了一個月還沒人處理的 PR。
2026年10月20日 · Waiting7777 · 7 分鐘閱讀
繼續閱讀 →
AI Agents自動化測試+1跑了兩週 Agent QA Pipeline — 真實數字、踩到的坑、重來我會怎麼做
這是 post-mortem,不是 case study。做對的三件事、做錯的三件事、以及為什麼這套東西接下來要抽成 `claude-qa-pipeline` OSS。
2026年10月20日 · Waiting7777 · 6 分鐘閱讀
繼續閱讀 →
自動化測試開發者工具你的 100% Coverage 是假的 — 用 test-audit 抓出沒被真正測到的分支
AI 寫的 code 跑出漂亮的覆蓋率數字,但 error path 根本沒人測過,這工具專門抓這個。
2026年10月20日 · Waiting7777 · 3 分鐘閱讀
繼續閱讀 →這篇文章對你有幫助嗎?
每週一篇 — 技術趨勢背後的商業邏輯
AI 產業在變什麼、工程師該注意什麼——拆清楚寄到你的信箱。