AI 寫的測試全過,然後 Production 就掛了:從 SQLite 災難看驗證設計的盲點

AI 生成的測試很容易給出「一切正常」的綠燈,但這種安全感往往是假象。一次真實的 SQLite 遷移專案中,AI 寫的測試全數通過,生產環境卻兩度崩潰。這並非 AI 的錯,而是我們對「測試」的期望出了問題。真正的挑戰不在於程式碼生成,而在於驗證設計——我們必須將測試的目標從「證明它能運作」,轉變為「探索它在何時會失效」。

AI 寫的測試全過,然後 Production 就掛了:從 SQLite 災難看驗證設計的盲點

AI 輔助開發時,我們常被「測試全綠」的虛假安全感蒙蔽。當 AI Agent 產出的測試案例 100% 通過 CI/CD,我們便傾向於相信程式碼穩固。然而,最近在 Hermess Autopilot 專案的 SQLite 資料庫遷移經驗,給了我當頭棒喝:測試的價值不在於通過,而在能否揭露問題。我們的程式碼在所有測試都綠燈的情況下,部署到生產環境後卻應聲崩潰。這暴露出根本性的工程問題:若不將測試視為一種「驗證設計」,強制模擬真實世界的壓力與混亂,AI 產出的測試,最終只是在替我們未來的災難背書。

AI 測試全過,生產環境卻崩潰:綠燈下的謊言是什麼?

在軟體開發流程中,自動化測試是品質保證的基石。當我們引入 AI Agent 來加速開發時,很自然會要求它「順便把測試也寫好」。在這次的專案中,AI 確實交付了看似完整的測試套件,涵蓋了所有功能變更,而且在持續整合(CI)環境中跑得非常順利,一片綠燈。

我們滿懷信心地將變更部署上線,然後,系統就掛了。問題排除後,我們發現了一個 AI 測試完全沒涵蓋到的盲點。我們修復了它,加強了提示(Prompt),讓 AI 重新生成更全面的測試,再次看到一片綠燈。然後,第二次部署,系統又因為一個完全不同性質的盲點而再次崩潰。

這兩次連續的失敗揭示了一個殘酷的事實:AI 傾向於編寫「能通過的測試」,而不是「能發現問題的測試」。它擅長驗證程式碼在孤立、理想環境下的邏輯正確性,也就是所謂的「Happy Path」。但對於生產環境中那些真正致命的因素——高並行性、龐大資料量、網路延遲、資源競爭——AI 若沒有被明確指示,幾乎完全無法觸及。

我們當時犯的錯,是把測試當成一個程式碼生成的待辦事項,而不是一個需要嚴謹設計的工程挑戰。我們問 AI「程式碼能跑嗎?」,它誠實地回答「能」。但我們真正該問的是:「在什麼條件下,它會跑不下去?」

為什麼 AI 寫的測試無法捕捉真實世界的失效?

AI 模型,特別是大型語言模型(LLM),在生成測試時存在固有的偏見與侷限。它們的訓練資料庫充滿了 GitHub 上的開源專案,其中絕大多數是單元測試與功能測試。這些測試的目標是驗證特定函式或模組的行為是否符合預期,而不是整個系統在壓力下的表現。

這種偏見導致了幾個問題:

  • 缺乏對抗性思維:專業的測試或站點可靠性工程師(SRE)會像個攻擊者一樣,不斷尋找系統的弱點。他們會問:「如果一萬個使用者同時點擊這個按鈕會怎樣?」「如果資料庫延遲 500 毫秒會怎樣?」但 AI 的預設行為模式是合作,它會試圖證明你寫的程式碼是「對的」,而不是想辦法證明它是「錯的」。
  • 忽略整合與環境風險:我們的 SQLite 崩潰,問題不在於單一程式碼邏輯錯誤,而在於資料庫層級的鎖定(locking)機制。在高並行請求下,SQLite 的寫入鎖會導致請求大量堆積,最終造成服務無回應(hang)。這種整合層面的風險,是單元測試永遠無法觸及的。一份針對 ChatGPT 生成測試的實證研究也指出,模型雖然能生成語法正確的測試,但在理解複雜的程式碼上下文與依賴關係方面仍有困難。
  • 對「狀態」的理解薄弱:真實世界的系統是有狀態的,而且狀態會隨時間與負載而變化。AI 生成的測試通常是無狀態或基於簡單的初始狀態,它們無法模擬長時間運行後,因記憶體洩漏、快取污染或資料碎片化導致的緩慢失能。

最終,AI 產出的測試創造了一個完美的「無菌室」,我們的程式碼在裡面表現優異,但一進入生產環境這個充滿細菌與壓力的真實世界,就立刻感染、休克。

如何將「模擬現實」注入 AI 測試流程?

既然意識到問題所在,解決方案也變得清晰。我們不能再被動地接受 AI 給出的測試,而必須主動出擊,像設計系統架構一樣去設計我們的「驗證策略」。這意味著在給予 AI Agent 指令時,必須包含對生產環境條件的明確要求。

我們的核心原則是:把壓力測試與邊界測試,直接寫入回歸測試(Regression Test)中。

具體來說,我們現在要求 AI Agent 在生成測試時,必須遵守以下幾點:

  1. 模擬生產等級的負載:明確要求測試案例必須包含高並行性與大數據量的場景。例如,指令可以是:「請為此 API 撰寫一個回歸測試,模擬 500 個並行連線,並處理一個超過 1GB 的資料集。」這會強制 AI 思考資源管理與競爭條件,而不僅僅是功能邏輯。這也是負載測試(Load Testing)的核心精神。
  2. 將「無回應」視為失敗:系統卡死(hang)或死鎖(deadlock)是比直接崩潰更陰險的敵人。我們要求所有測試都必須加上「看門狗計時器」(Watchdog Timer)。如果一個測試的執行時間超過預設閾值(例如 30 秒),就直接標記為失敗。這能有效地捕捉到那些在壓力下才會出現的效能瓶頸與死鎖問題。
  3. 引入混沌與隨機性:除了固定的壓力場景,我們也開始導入混沌工程(Chaos Engineering)的初階概念。例如,要求 AI 在測試中隨機注入延遲、模擬網路分區,或是在處理過程中隨機丟棄部分資料。這有助於我們驗證系統的容錯與恢復能力,類似於模糊測試(Fuzz Testing)在安全領域的應用。

這個轉變的本質,是將人類的經驗與判斷力,從「親手寫測試案例」提升到「設計測試的環境與約束條件」。我們不再是運動員,而是制定比賽規則的教練。

從程式碼生成到系統驗證的思維轉變

這次的 SQLite 災難,雖然痛苦,卻是一次寶貴的教訓。它讓我們明白,AI Agent 在軟體工程中的價值,不僅僅是作為一個「程式碼打字機」。如果我們僅僅用它來完成瑣碎的編碼與測試任務,我們就浪費了它真正的潛力,同時也為自己埋下了巨大的技術債。

真正的挑戰,是將我們的思維從「程式碼生成」提升到「系統驗證」的層次。人類工程師的價值,在於我們能夠預見風險、理解複雜系統的動態行為,並設計出能夠暴露這些風險的實驗。AI 則是執行這些實驗、並將其自動化、規模化的最佳工具。

未來,當我再次對 AI Agent 說「請幫我寫測試」時,我會補上最關鍵的那一句:「並且,請想盡辦法證明我是錯的。」

延伸閱讀

我是江中喬,專注於 AI Agent Architecture、Memory Governance 與 Cognitive Diversity,持續研究如何打造能夠長期協作、可信任且可治理的 AI 系統。