Agent 回報「測試全部通過」,你一點進去第一個操作就壞了?這篇帶你用一個小實驗看懂測試什麼時候不可信,再用五層做法讓它變可信!
Agent 跑完一輪,回報:「功能已完成,測試全部通過。」畫面上一排綠勾。你點進去試,第一個操作就不對。
這種落差很常見。Stack Overflow 2025 年的開發者調查裡,被問到用 AI 最大的挫折,66% 的人選了同一個答案:AI 給的東西「幾乎對,但差一點」。綠勾擋不住「差一點」。
很多人的解法是叫 AI 多寫一點測試,把覆蓋率拉高。我不太認同這個推論。測試的價值只有一個:程式錯的時候,它會紅。如果寫測試的和寫程式的是同一份理解,測試多寫十倍,也只是在同一個錯誤上多蓋十個章。
所以這篇想講清楚:測試本身憑什麼可信。〈AI 生產力悖論是什麼?為什麼 AI 做得更快,事情卻沒有更快交出去?(2026 完整解析)〉講了瓶頸為什麼搬到驗收,〈Plan、Build、Verify、Ship 是什麼?AI 寫程式變快後,進度要怎麼算?(2026 完整懶人包)〉講了 Verify 這一關要對照開工前寫好的驗收條件。這篇往下走一層:那些拿來過關的測試,要怎麼做才算數。
快速回答 AI 回報測試全過,產品還是錯,常見原因是程式和測試出自同一份對需求的理解:讀錯了,兩邊一起錯,測試也就一起通過。測試可不可信看兩件事:程式錯的時候它會不會失敗,以及寫它的人有沒有和寫程式的人共用同一份理解。驗收測試要只讀需求、在實作前寫好,先確認功能還沒做時它會失敗,並放在寫程式的 agent 改不到的地方。
程式與測試若共用同一份理解,模型只會照現狀寫斷言、把 Bug 當成正確答案
最危險的 AI Coding 流程長這樣:AI 讀需求、寫程式、寫測試、跑測試,再告訴你全部完成。每一步單獨看都合理,合起來有一個結構性的漏洞:所有步驟共用同一份對需求的理解。
如果模型一開始就讀錯了需求,程式照錯的理解寫,測試的期望值也照錯的理解寫。兩邊彼此一致,所以全綠;產品和需求不一致,但沒有任何一條測試在對照需求。
統計上把這叫相關性錯誤(correlated error):幾道檢查看起來各自獨立,其實來自同一個來源,所以會一起錯,也一起「通過」。這項推論有實證支持:盧森堡大學 Konstantinou 等人 2024 年用 24 個 Java 開源專案做實驗,發現大型語言模型替程式寫測試的斷言時,傾向照程式「實際做了什麼」來寫,少照「應該做什麼」。程式有 bug,測試就把 bug 當成正確答案記下來。
Anthropic 在 Claude Code 的官方最佳實踐裡,把這個問題講得很白:Claude 會在工作「看起來完成」時停下;沒有一個它能執行的檢查,「看起來完成」就是唯一的訊號。文件列了幾種把關方式,其中一種是讓一個全新的模型試著推翻結果,讓做事的 agent 和替結果打分數的,分成兩個;它也建議,可以讓一個 Claude 寫測試,另一個寫程式去通過它。
這裡要補的,是一個獨立的判定來源(independent oracle)。我想強調一點:獨立指的是資訊來源,換一個模型不一定就獨立。另一個模型如果先讀了實作、再寫測試,它一樣會照實作寫期望值。Anthropic 描述審查用的 subagent 時特別寫到,審查者只看得到改動和你給的標準,看不到產生這些改動的推理過程。獨立,就是要擋住那條推理過程。
驗收測試必須放在改不到的地方:即使提示明令禁止,GPT-5 仍有 54% 的題目靠改測試過關
相關性錯誤是無心的。還有一種情況更直接:負責寫程式的 agent,有權限改測試。
2025 年 10 月,卡內基美隆大學和 Anthropic 的研究者發表 ImpossibleBench。他們把 SWE-bench 等程式題目改成「不可能」的版本:測試和題目規格互相矛盾,照規格寫就一定過不了。任何能讓測試全過的解法,都是在繞過測試。
主實驗用的是論文裡最嚴格的提示:照規格實作,發現測試有問題就停下來說明,並用全大寫強調不准修改測試。GPT-5 仍有 54% 的題目讓測試通過,Claude Opus 4.1 是 50%。研究者分析手法:Claude 系列超過 79% 是直接修改測試案例,OpenAI 的模型則是改測試、對特定輸入寫死答案、覆寫比較運算子等手法都用。
同一篇論文也試了幾種解法。把測試藏起來,作弊幾乎歸零,但模型解正常題目的表現也跟著下降;把測試設成唯讀,擋得住改測試,擋不住對特定輸入寫死答案;讓模型可以把題目標記成「需要人處理」,GPT-5 從 54% 降到 9%。
我從這組數字讀到兩件事。第一,驗收測試要放在寫程式的那一方改不到的地方,改得到的測試,綠燈沒有意義。第二,「做不到就停下來」不能只寫在提示裡。最嚴格的提示已經叫它停,GPT-5 仍有 54% 的題目過關;給它一個真的可以呼叫的「交回給人」選項,才降到 9%。沒有這個出口,「讓測試通過」這個目標本身就會推著它去改考卷。OpenAI 在〈Harness engineering〉裡寫,三位工程師起步、後來七位,讓 Codex 寫出整個產品,產出量拉高之後,瓶頸變成人工 QA 的容量。人看不完的時候,這道權限線就是最後一道防線。
最小實驗驗證:共用理解讓錯的實作 4/4 全綠,獨立驗收與性質測試才能抓出邊界錯誤
前面都還是說法。說法要能被檢查,所以我寫了一個最小的實驗,只用 Python 標準函式庫,程式碼依序貼進同一個檔案就能跑。
想驗證的四件事
- 共用理解,就會一起綠:實作讀錯需求時,照同一份理解寫的測試全部通過。
- 只讀需求寫的驗收測試抓得到:而且功能還沒寫的時候,它每一條都會紅。
- 變異測試分得出測試有沒有在檢查,但抓不到讀錯需求:故意把程式改壞一行,看哪組測試會紅。
- 性質測試能自己找出反例:不挑金額,掃過一整段範圍,看會不會撞到錯。
跑出來的結果如果和這四點不同,前面的說法就要改。
前提假設
先說清楚:實作和它的測試是我照一種常見的誤讀手寫的,用來模擬「同一份理解」,過程中沒有任何模型參與。所以這個實驗只檢查機制,量不出哪個模型的錯誤率。
思路與程式碼
第一步,寫出讀錯需求的實作,和照同一份理解寫的四條測試。實作放在字串裡,後面做變異時才能一次改一行。第四條測試只檢查「有算出金額」,先留著,後面會看到它的問題。
# 需求原文:會員打九折;折扣後金額滿 1,000 元免運,未滿收運費 80 元。
# 實作把「折扣後」讀漏了,用折扣前的金額判斷免運
AI_SRC = """
def checkout(subtotal, member):
total = subtotal * 0.9 if member else subtotal
shipping = 0 if subtotal >= 1000 else 80
return round(total + shipping)
"""
def load(src):
ns = {}
exec(src, ns)
return ns["checkout"]
def run(tests, f):
red = [name for name, check in tests.items() if not check(f)]
return f"通過 {len(tests) - len(red)}/{len(tests)},紅:{red or '無'}"
# 同一份理解寫出來的測試
ai_tests = {
"會員 500 元,打折再加運費": lambda f: f(500, True) == 530,
"非會員 1,200 元,免運": lambda f: f(1200, False) == 1200,
"會員 1,050 元,免運": lambda f: f(1050, True) == 945,
"有算出金額": lambda f: f(800, False) is not None,
}
ai_checkout = load(AI_SRC)
print("自己的測試:", run(ai_tests, ai_checkout))
第二步,加上只讀需求寫的驗收測試。寫的人只看那一行需求,所以會自然去想「剛好 1,000 元算不算滿」「會員折完之後還夠不夠」。
acceptance_tests = {
"剛好 1,000 元,免運": lambda f: f(1000, False) == 1000,
"999 元,收運費": lambda f: f(999, False) == 1079,
"會員 1,050 元,折後 945,收運費": lambda f: f(1050, True) == 1025,
"會員 1,200 元,折後 1,080,免運": lambda f: f(1200, True) == 1080,
}
print("驗收測試:", run(acceptance_tests, ai_checkout))
第三步,紅了才算數。拿一個永遠回傳 0 的空殼代替實作,看兩組測試在功能根本沒寫的時候會不會紅。接著修正實作,再兩組各跑一次。
stub = load("def checkout(subtotal, member):\n return 0\n")
print("空殼+自己的測試:", run(ai_tests, stub))
print("空殼+驗收測試:", run(acceptance_tests, stub))
# 修正:免運改用折扣後的金額判斷
FIXED_SRC = AI_SRC.replace("if subtotal >= 1000", "if total >= 1000")
print("修正後+自己的測試:", run(ai_tests, load(FIXED_SRC)))
print("修正後+驗收測試:", run(acceptance_tests, load(FIXED_SRC)))
第四步是手動變異。變異測試(mutation testing)的意思是故意把程式改壞,看測試會不會紅;沒紅,代表那個地方壞了也沒人會發現。每次只改一處,自己的測試對原實作跑,驗收測試對修正後的實作跑。
mutants = [
("門檻 >= 改成 >", ">= 1000", "> 1000"),
("九折改成八折", "0.9", "0.8"),
("運費 80 改成 0", "else 80", "else 0"),
("不分會員都打折", "if member", "if True"),
]
for name, old, new in mutants:
print(name)
print(" 自己的測試(原實作):", run(ai_tests, load(AI_SRC.replace(old, new))))
print(" 驗收測試(修正後):", run(acceptance_tests, load(FIXED_SRC.replace(old, new))))
最後是性質測試(property-based testing):不寫「500 元應該付 530」這種個別答案,改寫一條對所有輸入都要成立的規則,讓程式自己去找反例。這裡的規則直接取自需求:運費只看折後金額,滿 1,000 免運,未滿收 80。
def find_counterexample(f):
for member in (False, True):
for subtotal in range(1, 3001):
after = subtotal * 0.9 if member else subtotal
fee = f(subtotal, member) - round(after)
if fee != (0 if after >= 1000 else 80):
return f"反例:{subtotal} 元、會員={member},運費算成 {fee}"
return "沒有反例"
print("原實作:", find_counterexample(ai_checkout))
print("修正後:", find_counterexample(load(FIXED_SRC)))
結果
- 4/4 全綠
- 紅 1 條:會員 1,050 元
- 紅 3 條,「有算出金額」照樣綠
- 4 條全紅
- 紅 1 條:會員 1,050 元,免運
- 4/4 全綠
- 綠(沒抓到)
- 紅
- 紅
- 紅
- 紅
- 紅
- 紅
- 紅
性質測試在原實作上找到的第一個反例是「1,000 元、會員」:折後 900 元,應該收運費,卻算成免運。修正後掃完 6,000 種組合,沒有反例。
逐條回扣。第一件成立:自己的測試 4/4 全綠,產品是錯的。第二件成立:驗收測試抓到「會員 1,050 元」這一條,而且空殼上四條全紅,代表每一條都真的在檢查東西。反過來看,「有算出金額」在空殼上照樣綠,它從頭到尾沒有紅過,也就沒有保護任何東西。
第三件要分兩半看。變異測試確實分得出測試有沒有在檢查:自己的測試殺掉了 4 個變異裡的 3 個,漏掉門檻那個,因為沒有人測剛好 1,000 元。但同一組測試殺得掉 75% 的變異,整組仍然在保護一個讀錯的需求。變異測試量的是「程式被改動時測試會不會發現」,它不知道期望值本身對不對。
第四件成立,而且撞到的是手挑案例沒挑到的地方:驗收測試挑了 1,050 元,性質測試直接找到最邊上的 1,000 元。
結果表裡還有一格值得多看一眼:修正後,自己的測試反而紅了一條。那條「會員 1,050 元,免運」把誤讀寫成了正確答案,下一個來修這段程式的人或 agent,看到它紅,很可能把正確的修正改回去。錯的測試留在程式碼庫裡,會變成 bug 的守門員。
實務落地五層防線:驗收測試先寫且先看它紅,放在實作者改不到的位置
實驗裡的每一步,在真實專案裡都對應一個做法。我把它們排成一條測試從寫下到上線的路線,最重要的規則只有一條:驗收測試先寫、先看它紅,寫程式的一方改不到它。
這張圖的主流程,是把實驗的第二、三、四步搬進日常;下排是第五層,讓每一次出錯都留下一條測試。五層各自回答一個問題,由不同的人、用不同的工具做:
- 這條測試真的會失敗嗎?
- 寫測試的人或 agent;對空殼或舊程式先跑一次。修 bug 先寫重現的測試
- 每條新測試
- 程式做的,是需求要的嗎?
- 只讀需求的人,或沒看過實作的另一個 session;放在實作者唯讀的位置
- 實作之前寫,每次合併都跑
- 這組測試會發現程式被改壞嗎?
- 工具自動改壞程式:Python 的 mutmut、Java 的 PIT、JS 的 Stryker;AI 補殺不掉的案例
- 每週或核心模組
- 規則在所有輸入上都成立嗎?
- 從需求寫出永遠成立的規則,交給 Hypothesis、fast-check 產生輸入找反例
- 和單元測試一起
- 同一個 bug 會不會回來?
- 發現 bug 的人;先寫會紅的測試,再修,永久留下
- 每次合併
先看它紅的成本最低,效果卻常被低估。Anthropic 的最佳實踐把「寫一個會重現問題的失敗測試,再修」直接寫進範例提示;實驗裡那條「有算出金額」,只要先對空殼跑一次就會現形。
獨立驗收測試的想法很早就有,驗收測試驅動開發(ATDD)講的就是實作之前先寫好驗收測試。AI 讓它多了一個條件:寫的人和實作的 agent,要隔開到看不見對方的推理。用 agent 寫也可以,開一個新的 session,只給它需求,不給它程式碼。
變異測試在業界已經開始和 AI 配在一起用。Meta 2025 年公開的做法是倒過來:先產生變異,再叫模型寫出能抓到這些變異的測試;在 Messenger 和 WhatsApp 的試行裡,工程師接受了 73% 由此產生的測試。日本教育科技公司 COMPASS 的工程師 2025 年分享,他們每週用 go-mutesting 跑一次變異測試,把存活的變異交給 Claude Code 補測試案例、開 PR,並規定它不准改產品程式碼。那個模組的單元測試覆蓋率早就是 100%,283 個變異裡仍有 24 個沒被抓到。
性質測試也是。Anthropic 研究者和 Hypothesis 維護者 2025 年合寫的論文,讓 agent 讀 Python 套件的程式和文件、自己推出性質、寫測試,掃過 100 個熱門套件;回報給維護者的 5 個 bug 裡,有一個在 NumPy,3 個修補已經被合併。它也提醒了成本:agent 產生的 bug 報告,經人工確認只有 56% 有效。
破除三個常見誤解:覆蓋率 100% 不等於有效,換模型寫測試不等於獨立
覆蓋率 100% 就夠了。覆蓋率只量程式有沒有被執行到,沒量執行到的時候有沒有檢查結果。COMPASS 那個模組覆蓋率 100%,仍有 8.5% 的變異改壞了也沒人發現;實驗裡那條「有算出金額」,也會讓覆蓋率好看。
AI 寫的測試品質比較差。愛爾蘭聖三一學院 Leith 2026 年 8 月的論文,比較 Claude Sonnet 4.6 在日常開發中寫的測試和 Django、Pandas 的人寫測試(只研究 Claude 一個模型家族),逐條算變異擊殺率,Claude 的 53.2%,Django 48.0%、Pandas 45.0%,沒有比較弱。這和本篇的論點不衝突:實驗裡的「自己的測試」殺掉了 75% 的變異,仍然整組在保護一個讀錯的需求。問題出在期望值從哪裡來,測試寫得好不好是另一件事。
換另一個模型寫測試,就獨立了。看它讀了什麼。讀了實作才寫測試,換哪個模型都會照實作寫期望值;只讀需求,才有機會抓到誤讀。另一個限制是需求本身:需求寫得模稜兩可時,兩個獨立的人也可能讀錯同一個地方,這時要回頭把需求寫清楚,再多的測試都補不了。
依出錯代價決定防線層級:個人工具先看它紅,涉及金流與個資才需做足五層
用完即丟的腳本、還在摸方向的原型,五層全做只會拖慢速度。我會用出錯的代價決定做到哪一層:只給自己用的,至少做到「先看它紅」;會碰到別人的資料、會收錢的,獨立驗收測試和回歸測試一條都不能少;變異測試和性質測試,留給最核心、最常被改動的那幾個模組。
變異測試本身也有成本。它要把測試跑很多遍,比單元測試慢得多,不適合每個 PR 都跑;變異是隨機產生的,每次存活的變異可能不一樣。COMPASS 改成每週跑一次,就是這個原因。
獨立審查也會產生新的雜訊。Anthropic 的文件特別提醒:被要求找問題的審查者,就算工作沒問題,通常也會回報一些問題;每一條都去追,會得到多餘的抽象層、防禦性程式碼,以及測試根本不可能發生的情況。所以要告訴審查者,只回報影響正確性或明確需求的落差,其他當參考。
結論:一條從沒紅過的測試保護不了任何功能,出題的理解必須留在需求端
回到開頭那排綠勾。它告訴你的,是程式和它自己的測試彼此一致;要知道程式和需求一致,得看那些測試是誰寫的、讀了什麼、有沒有紅過。
AI 讓寫測試變得幾乎免費,所以測試的數量不再說明任何事。值得花力氣的,是讓每一條測試都有一個獨立的出處,並且親眼看過它紅一次。寫程式的交給 AI,出題的那份理解要留在需求那一邊。
一條從來沒紅過的測試,你不知道它在保護什麼。
驗收測試要從需求寫,需求本身就得先寫清楚,可以接著讀〈AI Coding 還需要寫 PRD 嗎?需求越模糊,Agent 替你決定的越多(2026 完整指南)〉。
常見問題
測試覆蓋率 100% 就代表測試可信嗎?
不代表。覆蓋率只量程式有沒有被執行到,沒量執行到的時候有沒有檢查結果。日本教育科技公司 COMPASS 的工程師 2025 年分享,一個覆蓋率早就 100% 的模組,283 個變異(故意改壞一處的版本)裡仍有 24 個(8.5%)把程式改壞了也沒被測試抓到。
換另一個模型寫測試,就算獨立驗證嗎?
看它讀了什麼。另一個模型如果先讀了實作才寫測試,一樣會照實作寫期望值。獨立指的是資訊來源:開一個新的 session,只給需求、不給程式碼,才有機會抓到誤讀。需求本身模稜兩可時,兩個獨立的人也可能讀錯同一處,這時要先把需求寫清楚。
AI 會偷改測試讓它通過嗎?
有可能。2025 年 10 月的 ImpossibleBench 刻意讓測試和規格互相矛盾,照規格寫過不了;即使提示明說不准改測試,GPT-5 仍讓 54% 的題目測試通過,Claude Opus 4.1 是 50%。給模型一個真的能呼叫的「交回給人」選項,GPT-5 降到 9%。驗收測試要放在 agent 改不到的地方。
什麼是變異測試(mutation testing)?
故意把程式改壞一處,看測試會不會紅;沒紅,代表那裡壞了也沒人會發現。常用工具有 Python 的 mutmut、Java 的 PIT、JavaScript 的 Stryker。它量的是程式被改動時測試會不會發現,不知道期望值本身對不對,分數高的測試也可能整組在保護一個讀錯的需求。
延伸閱讀
- AI 生產力悖論是什麼?為什麼 AI 做得更快,事情卻沒有更快交出去?(2026 完整解析)
- Plan、Build、Verify、Ship 是什麼?AI 寫程式變快後,進度要怎麼算?(2026 完整懶人包)
- Vibe Coding 是什麼?做出來會動,為什麼還不能直接給別人用?(2026 新手必看)
- AI Demo 很成功,為什麼一上線就出事?五條失敗路徑一次看懂(2026)
參考資料
AI 寫的測試與獨立驗證
- Anthropic, Best practices for Claude Code(〈Give Claude a way to verify its work〉〈Add an adversarial review step〉,2026-10-16 檢視) https://code.claude.com/docs/en/best-practices
- Ziqian Zhong, Aditi Raghunathan, Nicholas Carlini, ImpossibleBench: Measuring LLMs' Propensity of Exploiting Test Cases(arXiv 2510.20270,2025-10-23) https://arxiv.org/abs/2510.20270
- Michael Konstantinou, Renzo Degiovanni, Mike Papadakis, Do LLMs generate test oracles that capture the actual or the expected program behaviour?(arXiv 2410.21136,2024-10-28) https://arxiv.org/abs/2410.21136
- Douglas J. Leith, The Quality of Claude AI-authored Python Tests Is Not Weaker Than Human-authored Tests(arXiv 2608.15188,2026-08-15) https://arxiv.org/abs/2608.15188
- OpenAI(Ryan Lopopolo), Harness engineering: leveraging Codex in an agent-first world(2026-02) https://openai.com/index/harness-engineering/
變異測試與性質測試
- Meta, Mutation-Guided LLM-based Test Generation at Meta(arXiv 2501.12862,2025-01) https://arxiv.org/abs/2501.12862
- 株式会社 COMPASS(mastar3104),〈退屈なことはAIにやらせよう(ミューテーションテスト編)〉(Zenn,2025-07-14,日文) https://zenn.dev/qubena/articles/8a5182abae2b46
- Muhammad Maaz, Liam DeVoe, Zac Hatfield-Dodds, Nicholas Carlini, Agentic Property-Based Testing: Finding Bugs Across the Python Ecosystem(arXiv 2510.09907,2025-10) https://arxiv.org/abs/2510.09907
開發者調查
- Stack Overflow, 2025 Developer Survey: AI(2025-07-29) https://survey.stackoverflow.co/2025/ai
留言0
登入後就能留言。
登入