AI 幫你寫的測試全綠,為什麼上線還是壞掉?
871 支測試全綠,卻連畫面被彈窗擋死兩天都抓不到。這篇順著我們踩過的坑,拆開 AI 假測試的長相、怎麼親眼驗出測試有沒有用,並附上一份可直接貼給 AI 的體檢 Prompt。
這篇的段落(5)
用 AI 寫程式一段時間之後,多數人都會養出一個好習慣:別只聽它說「改好了」,要叫它補測試、看綠燈。但 AI 寫出來的綠燈也會騙人,而且騙得比沒寫測試還徹底。
10 月中,我請 AI 在自己的即時互動教學系統加一個小功能:學生第一次輸入名字加入課堂時,畫面先彈出一張四位數「找回碼」小卡讓他存成圖片。功能寫好後我按下 git push,本機幾百支測試全部綠燈。
結果合併上線才發現,「老師開課+學生加入」的核心流程,早就因為那張新彈窗擋在畫面正中央而卡死兩天。那時專案帳面上有 871 支測試、三十多道檢查關卡,每次推送要等十幾分鐘,卻連最基本的「畫面被彈窗擋死」都沒抓到。
我叫 AI 全部停下來,把整條驗證流程砍掉重練。下面順著我們一路追問的順序,拆開這座假安心系統是怎麼長出來的,以及怎麼把它清乾淨。
測試明明寫了八百多支,為什麼連畫面壞掉都抓不到?
因為 AI 為了省事,根本沒把畫面跑起來,只是把原始碼當純文字讀進來找變數名。
當我回頭去查那支「守色彩對比度」的單元測試為什麼沒叫,才發現它從頭到尾沒有算過半個顏色,只是用正規表示式檢查檔案裡有沒有寫 --text-muted 這個字串。你叫 AI「幫這個功能補測試」卻沒限制寫法時,它最愛直接寫這兩行:
const source = fs.readFileSync('src/features/reset.ts', 'utf8');
expect(source).toMatch(/reset_project_data_v2/);這種測試跑起來只要 0.01 秒,永遠不會因為環境問題失敗,但它守的是檔案裡的字,跟使用者看到的功能毫無關係。我們一盤點,八百多支裡竟然有一百三十多支在幹這件事(另外還有在測試檔裡自己抄一份簡化邏輯測自己的「抄本測試」,以及檢查幾個月前早就刪掉的舊錯誤文案)。
那我叫 AI 把假測試刪掉重寫,總可以了吧?
直接叫它刪更危險,因為它連「別處已經測過了」都會看走眼。
清理第一天,AI 讀完程式碼跟我回報:「A 測試跟 B 測試重複了,B 已經完整覆蓋,所以我把 A 刪掉,測試全綠!」我不放心,手動把產品裡「儲存投影片樣式」的那行邏輯故意改壞,再跑一次測試。結果全專案一片綠燈,只有它剛才想刪掉的那支 A 測試會亮紅燈。原來 B 測試在畫面掛載後還沒等事件綁定好就急著點按鈕,從頭到尾都在測空氣。
所以我們立下一條死規矩:不聽 AI 口頭保證,想刪改任何測試,必須先通過下面的破壞驗證(Break-Check):
清完假測試後,我們還反過來抽考剩下的測試:不碰測試檔,隨機挑幾十個業務邏輯故意改壞(反轉分數判斷、拿掉權限檢查、關掉快取過期),結果抓出二十幾個只測成功路徑、沒測權限拒絕的漏網之魚,全數補到會亮紅燈為止。
測試都改對了,為什麼每次推程式碼還是要等十幾分鐘?
因為本機、PR 跟合併後這三道關卡一直在重複跑同一批慢檢查,真正會抓出線上故障的雙端測試卻拖到合併後才跑。
以前不管改兩行字還是改幾百個檔案,本機推送前都要跑完整打包與全套測試;推上 PR 後雲端從頭再跑第二遍;等合併進主線,才第一次去跑最核心的雙端互動流程。我們定了一條新原則,同一件事只跑一次,把真後端測試前移到 PR 階段,直接看下面的時間長條比例就能看出等待時間與攔截點的差距。
通道變快之後,我們順手清查了以前因為「跑太慢」被移出自動名單的二十多支孤兒瀏覽器測試。把不依賴真實排版的案例降到記憶體裡跑、剩下的孤兒重新排進 PR 自動關卡後,第一輪就爆出十個紅燈,全數抓到開頭說的那張找回碼彈窗。留在專案裡的測試,要嘛每輪自動跑,要嘛驗證後刪掉;躺在角落不跑的測試只會默默腐爛。
寫了九份原則文件叫 AI 別再犯,為什麼它讀完照樣寫錯?
因為寫在 Markdown 裡的規定對 AI 來說只是參考,只有編譯器跟靜態檢查畫的紅線它才繞不過去。
我們原本有九份近千行的原則文件,白紙黑字寫著「不要亂傳 ID」「不要寫讀原始碼的測試」,但 AI 讀完轉頭照犯。與其寫文件拜託 AI 記住,我們直接把規則沿著下面的五層階梯往上搬,讓錯的程式碼在存檔或推送那一刻就被工具擋死:
看到測試全綠,現在就能拿去考 AI 的體檢指令
下次看到測試一片綠燈,先問三個問題:把功能故意改壞一行會不會紅?這支測試有沒有在每次 PR 自動跑?這條規定能不能往上搬進型別或 ESLint?
如果你想讓 AI 幫你把專案裡的假測試、重複慢關卡與肥大規則檔清一遍,直接把下面這段體檢指令貼給 Claude Code、Cursor 或 Codex(第一輪先讓它交唯讀診斷報告,確認清單後再動手):
請對這個專案進行「測試真實性、CI 管線耗時與型別約束」唯讀體檢(先不要修改任何檔案),交出以下 4 份清單:
1. 假測試名單:找出所有用 readFileSync 讀原始碼比對字串、在測試裡重抄產品邏輯測自己、或斷言舊文案不存在的測試檔,逐支標註建議「改為行為測試」或「轉成 ESLint 規則後淘汰」。
2. 孤兒測試與慢關卡對照:列出本機 pre-push、PR CI、合併後各自跑了哪些指令與耗時,標出重複跑了兩遍以上的檢查,以及躺在目錄裡卻沒排進 PR 自動執行的孤兒測試。
3. 可升級為硬約束的規則:讀完 CLAUDE.md / AGENTS.md,挑出哪些文字規定可以直接升級成 Branded Types、集中註冊表、ESLint AST 規則或 dependency-cruiser 單向依賴鎖。
4. 分批改造計畫:每一批刪除或改寫測試前,必須先執行破壞驗證(故意改壞對應產品碼 → 證明接手測試亮紅燈 → 還原確認全綠),請列出第一批最值得動刀的 5 個檔案。拿到清單後,挑最痛的一批開始,親眼看到紅燈再放行。如果還不確定自己的專案最該先修哪一層,可以先做免費的 AI 工作流五層健檢,照題目、資料、工具、進度、驗收五個問題找出最划算的修法;想看我們怎麼在連半行原始碼都沒有的老系統裡做雙端反組譯對帳,也可以接著讀〈帶著 AI 把 2012 年的無原始碼老遊戲從零救活:多模型逆向除錯與本機 v1.0.0 優化實錄〉與〈Claude Code 實戰:我怎麼用 AI 寫完一整個網站〉。
