AI 的單元測試:為什麼它寫的測試測不到 bug

artificial%20intelligence%20concept%2C%20digital%2...
發表時間:2026 年 10 月 28 日 | 更新日期:2026 年 10 月 28 日 | 編輯:雅寶社區編輯團隊
AI 的單元測試:為什麼它寫的測試測不到 bug - 雅寶社區 · 頂客論壇

框架官方文件裡的範例測試(極度簡化、只驗證最基本行為)

教學文章與部落格裡的示範(刻意避開複雜邊界)

  • 熱門專案中經過多年演進、但已經被後人補齊的測試(你看到的是結果,看不到當初那些血淚邊界案例是怎麼被補上的)
  • 換句話說,模型學到的是「測試的平均長相」,而不是「測試的高標長相」。它不知道那些真正抓到 bug 的測試,往往是醜的、瑣碎的、針對某次深夜事故硬補上去的。而這些,恰恰是最有價值的測試。

    二、七個讓 AI 測試失效的結構性原因

    把上面這些原理進一步拆解,可以歸納出七個具體、可辨識的失效模式。理解這些模式,比單純抱怨「AI 寫的測試沒用」有價值得多,因為每一種都有對應的解法。

    原因一:把實作邏輯翻譯成斷言,形成同義反覆

    這是最常見的一種。假設你有一個計算運費的函式,內部有一段 if (weight > 20) return base * 1.5。AI 寫的測試就會是「體積 20 公斤以下的訂單,運費等於 base;21 公斤的訂單,運費等於 base × 1.5」。測試看起來很完整,覆蓋率很漂亮,但它是把實作抄了一遍。

    真正的測試應該問的是:「這個 1.5 倍的加價規則,是業務要的嗎?20 公斤這個門檻,是含 20 還是不含 20?超過 100 公斤時,業務上是不是根本應該走特殊報價而不是線性放大?」這些問題,AI 一個都問不出來,因為它不知道原本的規格長什麼樣子。

    同義反覆測試的危害在於它的欺騙性:它讓重構變得困難(因為它鎖死了實作細節),卻沒有提供任何保障(因為 bug 會被一起寫進預期值),而且它會大量佔用維護成本。

    原因二:只測快樂路徑,邊界條件系統性缺席

    單元測試真正抓到 bug 的比例,絕大多數集中在邊界條件與異常輸入上:空陣列、null、負數、極大值、超長字串、時區跨日、金額精度、並發寫入、重複呼叫。而這些,恰好是 AI 最少主動生成的東西。

    原因不難理解:邊界測試需要領域知識。它需要知道「這個欄位在資料庫裡是 varchar(50),但上游 API 沒有做長度驗證」,需要知道「這個時間欄位在跨日光節約時間時會出問題」,需要知道「這個折扣在金額為 0 元時會除以零」。這些知識不存在於被貼上的那段程式碼裡。

    更麻煩的是,即使你明確要求 AI「產生邊界測試」,它給的邊界往往是型別層面的邊界(例如 0、-1、Integer.MAX_VALUE),而不是業務層面的邊界(例如「會員等級剛好升等的那一筆訂單」)。前者看起來很全面,實際命中率很低,因為那些邊界在真實系統裡幾乎不會發生,而真正會發生的那些,AI 想都想不到。

    原因三:Mock 濫用,把整合行為測成空殼

    這是 AI 寫測試時最嚴重的結構性問題之一,而且非常隱蔽。

    當函式依賴了外部服務、資料庫、訊息佇列或第三方 API,AI 幾乎必然會用 mock 把它們全部替換掉。jest.mock()、@Mock、MagicMock 一個接一個,然後斷言「這個 repo 的 findById 被呼叫了一次」。測試通過了,覆蓋率上升了,但你實際驗證的東西是什麼?是「這段程式碼會去呼叫某個方法」,而不是「這段程式碼對資料做了正確的事」。

    更糟的情況是AI 會把 mock 的回傳值寫死成錯誤的假設。例如它假設 repo.findById() 找不到資料時會回傳 null,但實際上那個 ORM 的回傳是 Optional.empty() 或拋出 EntityNotFoundException。這種錯誤的約定在單元測試裡永遠不會被發現,因為 mock 是你自己控制的——它永遠會照你寫的樣子回傳。等到接上真的資料庫,整個流程就炸了。

    這正是所謂的「測試金字塔倒過來」:AI 幫你快速累積了大量的單元測試,但這些測試對真實系統的保障極低,因為它們驗證的是「程式碼與自己的一致性」,而不是「程式碼與外部世界的一致性」。

    原因四:為覆蓋率而生,而不是為失敗而生

    如果你請 AI「提高測試覆蓋率」,它會做得很好。它會系統性地掃過所有分支、為每個 if 寫一個案例、為每個 case 寫一個斷言,然後告訴你「覆蓋率從 62% 提升到 94%」。

    但覆蓋率衡量的是「程式碼被執行過」,不是「程式碼被驗證過」。一個沒有斷言的測試,也能貢獻 100% 的行覆蓋率。一段被執行過但沒有被質疑的程式碼,一個 bug 都不會被抓到。這是軟體工程領域早已被反覆討論的老問題,而 AI 把這個問題規模化了:它能在幾分鐘內產出上千行「執行得到但驗證不到」的測試,讓覆蓋率報表變得前所未有的漂亮,也前所未有的沒有意義。

    更精確地說,好的測試是由失敗驅動的:先想像一個錯誤情境,確認「如果程式真的這樣錯了,這個測試會紅」,然後才寫斷言。AI 的生成過程幾乎不包含這個「想像失敗」的步驟,它是從成功的情境往回推,所以它寫出來的測試天生就是為了變綠而存在的。

    原因五:缺乏需求脈絡,AI 不知道「應該是什麼」

    測試的本質是規格的執行版本。一份好的單元測試,其實是在回答「這個系統被要求做什麼」。而 AI 手上只有實作,沒有規格。

    這也是為什麼在需求頻繁變動的系統裡,AI 寫的測試特別容易失效:因為它把「當下的實作」固化成了「永恆的期望」。等需求變了、實作改了,這些測試不會提醒你「這裡的行為變了,業務上是否允許」,只會變成一片紅色,然後被工程師們一個一個刪掉或改成「符合新行為的預期值」——這個過程叫做測試橡皮圖章化,是測試品質崩壞的典型徵兆。

    原因六:AI 會「配合」錯誤的程式碼,甚至幫 bug 找理由

    這個現象在實務中相當常見,也最容易被忽略。當你把一段有問題的程式碼交給 AI,請它寫測試,模型往往會先在心裡(或實際輸出中)「合理化」這段程式碼的存在。例如看到一段沒有做 null 檢查的程式碼,它不會寫「輸入 null 應該拋錯誤」的測試,而是會寫「傳入合法物件時正常運作」的測試,並且在註解裡補一句「假設呼叫端已確保參數非 null」。

    這種「幫程式碼找台階下」的行為,源自模型被訓練成「解釋並接納使用者提供的脈絡」。它讓 AI 很難扮演對抗性審查者的角色。而測試工程師最核心的價值,恰恰就是對抗性——你要主動去戳、去疑、去假設最壞的情況。

    原因七:重現已修復 bug 的能力薄弱

    回到軟體工程的老智慧:每一個被修掉的 bug,都應該留下一個測試來鎖住它。這類「回歸測試」是單元測試裡價值最高的部分,因為它們對應的是真實發生過的災難。

    AI 在這件事上幾乎幫不上忙,原因是它不知道歷史。它不知道三個月前那個「會員生日當天折扣重複計算」的 bug 是怎麼發生的,不知道那次事故導致了多少客訴,不知道後來是怎麼修的。你把修好的程式碼給它,它只會看到「這裡有一個 if (isBirthday && !alreadyApplied)」,然後寫出一個驗證「生日且未套用時會打折」的測試——但它不會想到要去測「生日且已套用時不該重複打折」這個關鍵路徑,因為那個 ! 在它眼中只是實作細節,不是一段血淚史。

    三、比技術更麻煩的:組織與人性的因素

    如果問題只出在模型能力,那等下一代模型就好。但實務上,AI 測試失效的很大一部分原因,其實不在技術層面,而在我們怎麼使用它、怎麼衡量它、怎麼相信它。

    覆蓋率數字的安慰劑效應

    在多數組織裡,「測試有沒有做」被簡化成一個數字。當 AI 能在十分鐘內把覆蓋率從 40% 推到 90%,這個數字帶來的管理層安心感是巨大的。它會出現在週報裡、出現在 release checklist 裡、出現在 code review 的通過理由裡。

    但數字一旦變漂亮,質疑的動力就消失了。沒有人會再去問「這 90% 裡有多少是斷言了正確行為的」、「有多少是 mock 掉一切的空殼測試」、「有多少在重構時會被無腦改掉」。覆蓋率從診斷工具變成了績效指標,而任何變成績效指標的指標,都會被最佳化到失去意義——這是 Goodhart's Law 的經典案例,AI 只是把它加速了。

    審查疲勞與「看起來沒問題」

    AI 產出的測試在形式上通常很漂亮:命名規範、結構清晰、用 describe/it 分好層級、每個案例都有完整的前置設定。這種「整齊感」很容易讓 code review 的人放鬆警戒,尤其是當 PR 動輒三、五百行測試檔時,審查者通常只會掃一眼結構,確認「看起來合理」就按下 approve。

    但測試的價值藏在細節裡:那個 expect 斷言的到底是什麼?那個 mock 的假設對嗎?那個 @BeforeEach 設定的狀態有沒有讓測試之間的隔離失效?這些都需要逐行閱讀才能發現,而 AI 讓測試的產出速度遠遠超過人類的審查速度,於是審查被實質降級成了形式。

    「全綠」帶來的虛假安全感

    這是最危險的一環。CI 綠燈原本是「我有信心出貨」的訊號,當它變成「AI 幫我補了一堆測試所以應該是綠的」,這個訊號就失真了。團隊會開始在明明有風險的情況下,因為「測試都過了」而減少手動驗證、減少思考、減少對抗性討論。

    測試最重要的功能,從來不是驗證程式碼,而是迫使工程師在寫程式時想清楚各種情況。當這個思考過程被外包給 AI,測試就只剩下形式,而失去了它作為設計工具的本質價值。

    四、讓 AI 測試真正有用的做法

    講了這麼多失效模式,並不代表 AI 在測試領域沒用。恰恰相反,用對方法的話,它可以成為團隊裡最有效率的測試助手。關鍵是改變你交給它的任務:不要讓它「幫我寫測試」,而是讓它「幫我列舉我沒想到的情境」。

    先寫規格,再讓 AI 寫測試(規格驅動)

    這是最有效的一招。在讓 AI 寫測試之前,先用自然語言把預期行為寫清楚,包含:正常輸入應該得到什麼、異常輸入應該被如何處理、業務規則的邊界在哪裡、有哪些前置條件必須成立。然後把規格與程式碼一起交給 AI,要求它「產生測試,並且如果有任何規格沒有被程式碼滿足,請明確指出來」。

    這樣做有兩個好處。第一,AI 有了「應該是什麼」的參照,就不會單純從實作反推,而能做真正的比對。第二,這個過程會逼你先把規格想清楚——而這個「想清楚」的動作,本來就是測試設計中最有價值的部分。

    用變異測試當驗金石

    如果你想知道「AI 寫的測試到底有沒有用」,變異測試(Mutation Testing)是目前最直接的檢驗方式。它的原理是:故意在程式碼裡植入小錯誤(把 > 改成 >=、把 + 改成 -、刪掉一行、反轉條件),然後看測試會不會變紅。

    如果測試全部照樣綠燈,就代表這個測試根本抓不到錯——不管它覆蓋率多高、寫得多漂亮。變異測試的分數(Mutation Score)遠比覆蓋率誠實,因為它直接衡量「測試抓到錯誤的能力」。把 AI 產出的測試丟進變異測試跑一輪,你很快就會看到哪些測試是空殼。

    用 AI 找「你沒想到的場景」,而不是找斷言

    這是心態上的關鍵轉換。與其讓 AI 輸出成品的測試檔,不如讓它輸出清單:

    「這個函式可能被哪些奇怪的輸入呼叫?」

    「以你對這類系統的經驗,最常在哪裡出錯?」

    「如果資料是空的、重複的、順序顛倒的、並發的,分別會發生什麼?」

    「這段程式碼有哪些隱含假設?如果假設不成立會怎樣?」

    AI 在「列舉可能性」上的表現,遠比在「寫出正確斷言」上可靠。因為列舉不需要精確知道業務細節,只需要廣泛的知識;而斷言需要精確的規格。把 AI 放在它擅長的位子上,把判斷留給人。

    人機分工的新界線

    比較健康的分工方式是這樣的:人負責「測什麼」,AI 負責「怎麼寫」。人類工程師根據業務風險、歷史事故、需求變更,決定哪些行為必須被鎖住、哪些邊界必須被驗證;AI 負責把這些決定轉換成具體的測試程式碼、處理繁瑣的測試資料建構、產生各種參數化組合。

    另一個很有效的用法是逆向使用:給 AI 一段「已知有 bug」的程式碼,請它在不知道答案是 bug 的前提下寫測試,看看它能不能抓到。這是一個很好的檢驗方式,可以讓你建立對這個工具的信心邊界——你會很清楚知道它在什麼情況下可靠、什麼情況下不可靠。

    五、一份可落地的檢查清單

    最後,整理一份實務上可以直接拿來用的檢查清單。每次接收 AI 產出的測試時,逐項確認:

  • 斷言檢查:每個測試是否都有明確的 expect?斷言的是行為還是實作細節?
  • 紅燈檢查:如果我把實作改壞一行,這個測試會紅嗎?隨機挑三個測試試試看。
  • Mock 檢查:被 mock 掉的外部依賴,其行為假設是否與真實實作一致?有沒有 mock 到「什麼都沒測到」的程度?
  • 邊界檢查:測試裡出現了哪些邊界值?這些邊界是型別邊界還是業務邊界?業務邊界有沒有被遺漏?
  • 規格檢查:這些測試的預期值,是來自需求規格,還是從程式碼反推出來的?
  • 回歸檢查:這次修的 bug,有沒有對應的測試鎖住它?AI 有沒有想到那個導致 bug 的具體路徑?
  • 維護性檢查:如果三個月後需求變更,這些測試會是助力還是阻力?有多少測試只是在鎖死實作?
  • 變異測試檢查:定期跑一次變異測試,看看這批測試的實際抓錯能力。

    六、結語:測試的價值不在綠色,而在懷疑

    回到最初的問題:為什麼 AI 寫的測試測不到 bug?答案可以濃縮成一句話——因為 AI 不知道什麼叫做「錯」。

    它知道語法正確的測試長什麼樣子,知道常見的框架用法,知道怎麼把覆蓋率補滿。但它不知道業務上什麼是不該發生的,不知道歷史上哪些路徑出過事,不知道工程師在半夜被叫起來修的那些 bug 是怎麼釀成的。而測試的全部價值,就藏在這些它不知道的地方。

    這不代表我們該放棄 AI 輔助測試。它確實在減少重複勞動、加速樣板生成、擴充測試廣度上有巨大價值。但如果我們把它當成「測試問題的解答」,而不只是「測試工作的加速器」,那得到的就會是一份漂亮的、全綠的、讓人安心的——然後什麼都攔不住的測試套件。

    真正該守住的那條線是:測試是工程的懷疑行為,不是產出指標。AI 可以幫你更快地寫下你的懷疑,但它沒辦法替你產生懷疑。誰決定要懷疑什麼、要防範什麼、要為什麼負責——這件事,仍然只能是人。

    下次在按下「請 AI 產生測試」之前,先花五分鐘問自己:如果這段程式碼會在三個月後爆掉,最可能是因為什麼?把那個答案寫下來,再交給 AI 去寫測試。你會發現,這一小段前置思考,比整批自動生成的測試檔有價值得多。

    ```

    🏠 返回首頁