一句話說明
AI 寫單元測試的效率非常高,特別是純函式和邊界值測試。但 AI 寫的測試有個根本的盲點:它只能測試它理解的行為,而不是你期望的行為。這篇整理 AI 擅長和不擅長的測試類型、循環驗證的問題、以及讓 AI 產生有效測試的 Prompt 技巧。
AI 擅長寫的測試
以下是 AI 測試生成效果最好的幾類:
純函式的單元測試。輸入固定、輸出固定、沒有副作用的函式,AI 可以快速列舉各種輸入組合。例如一個計算折扣的函式,AI 可以產生正常值、邊界值(0 元、負數、超大金額)、特殊條件(會員折扣 + 滿額折扣疊加)的測試案例。
邊界值測試。AI 很擅長想到邊界條件:空字串、null、undefined、超長字串、0、-1、MAX_INT、空陣列、巢狀物件。這些是人類開發者最容易漏掉的測試案例。
資料轉換函式。把 CSV 轉成 JSON、日期格式轉換、單位換算——這類函式的輸入輸出對應關係明確,AI 可以產生大量的測試資料。
正則表達式測試。給 AI 一個 regex,它可以產生應該匹配和不應該匹配的字串清單。這比手動想測試字串快得多。
錯誤路徑測試。告訴 AI「這個函式有哪些可能的錯誤情況」,它可以列出 timeout、network error、permission denied、disk full、invalid JSON 等情境並產生對應的測試。
AI 不擅長寫的測試
整合測試(Integration Test)。需要連接真實資料庫、呼叫外部 API、操作檔案系統的測試。AI 可以寫出程式碼框架,但不知道你的測試環境設定(測試用的資料庫在哪、要怎麼初始化測試資料、外部 API 的 mock 該回什麼)。
端對端測試(E2E Test)。使用者操作流程的測試(開啟頁面 → 點擊按鈕 → 填表單 → 送出 → 檢查結果)需要了解 UI 的實際行為和狀態變化。AI 能寫出 Playwright 或 Cypress 的程式碼骨架,但細節(等待時間、元素選擇器、非同步狀態管理)通常要手動調整。
安全測試。SQL Injection、XSS、CSRF、權限繞過——這些攻擊的測試案例需要攻擊者思維。AI 可以產生基本的 payload(' OR 1=1 --、<script>alert(1)</script>),但進階的繞過技巧(encoding、double encoding、unicode normalization)和邏輯漏洞的測試,AI 的品質不穩定。
競態條件測試。兩個使用者同時購買最後一件商品、兩個 API 同時修改同一筆資料——這些需要精確控制時序的測試,AI 很難正確設計。
效能測試。負載測試的參數(並發數、ramp-up 時間、持續時間)需要根據實際系統的容量規劃來設定,AI 缺乏這個上下文。
循環驗證問題
用 AI 測試 AI 寫的程式碼有一個根本問題:如果 AI 對業務邏輯的理解有錯誤,它寫的程式碼和測試會犯同樣的錯誤。
範例:假設業務規則是「訂單金額超過 1000 元免運費」。AI 把條件寫成 if (amount > 1000)(不含 1000 元),然後 AI 寫的測試也會預期 1000 元要收運費。程式碼和測試都通過了,但業務規則是「超過 1000 元」,1000 元應該要免運費,正確的條件是 if (amount >= 1000)。
這就是循環驗證:AI 用自己的理解驗證自己的實作,錯誤互相掩蓋。
解決方法:
把業務規則寫成明確的規格,讓 AI 根據規格寫測試,而非根據程式碼寫測試。
Prompt 範例:
以下是折扣規則的規格:
- 訂單金額 >= 1000 元:免運費
- 訂單金額 500-999 元:運費 60 元
- 訂單金額 < 500 元:運費 120 元
- 離島地區一律加收 80 元
請根據這個規格寫測試,不要看程式碼的實作
這樣 AI 會根據規格中的數字寫出測試案例,而不是去猜程式碼的行為。人類再 review 這些測試案例是否完整涵蓋了所有邊界。
另一個做法:讓 AI 寫程式碼,讓人寫測試。或者讓 AI 寫測試,讓人寫程式碼。拆開「寫程式」和「寫測試」的角色,避免同一個 AI 實例用同樣的理解做兩件事。
產生有效測試的 Prompt 範例
基本的測試生成 prompt(效果普通):
幫這個函式寫測試
有效的測試生成 prompt:
幫 calculateShipping(amount, region) 寫 Jest 測試。
要求:
1. 覆蓋以下邊界值:0, 499, 500, 999, 1000, 1001, -1
2. region 測試:'台灣本島', '離島', '金門', '', null
3. 每個測試案例用 describe/it 結構,描述用繁體中文
4. 錯誤情境:amount 不是數字、region 不在允許清單中
5. 不要 mock 任何東西,這是純函式
6. 預期結果根據以下規格(不要看實作):
- amount >= 1000 且本島:0 元
- amount 500-999 且本島:60 元
- amount < 500 且本島:120 元
- 離島加收 80 元
- 無效輸入 throw Error
這個 prompt 有效的原因:
指定了邊界值(不讓 AI 自己想,因為它可能漏掉)。指定了輸入組合(region 的各種值)。提供了預期結果的規格(避免循環驗證)。明確說不要 mock(純函式不需要)。指定了測試框架和風格(Jest + describe/it + 中文描述)。
針對安全測試的 prompt:
幫 loginHandler(req, res) 寫安全測試。
要測試:
1. SQL Injection:username 帶 ' OR 1=1 --
2. NoSQL Injection:username 帶 {"$gt": ""}
3. XSS:username 帶 <script>alert(1)</script>
4. 暴力破解防護:連續 10 次失敗登入後應該被 rate limit
5. 時序攻擊:不存在的使用者和密碼錯誤的回應時間不應該有顯著差異
6. 回應資訊洩漏:錯誤訊息不應該區分「使用者不存在」和「密碼錯誤」
每個測試要有明確的 assert,不只是「不會 crash」
AI 測試工具比較
GitHub Copilot:在測試檔案中打 describe,它會自動補全測試案例。速度最快但深度最淺,通常只產生 happy path。
Cursor(Composer):可以在 Composer 中同時看到原始碼和測試檔案,用 @ 引用兩者後,產生的測試品質較高。適合需要跨檔案理解的測試。
Claude Code:在終端機中可以讓它讀取整個專案後寫測試,而且可以直接跑 npm test 驗證測試通過。它會自動修正失敗的測試(但要注意是修正測試還是修正被測的程式碼——兩者意義不同)。
飛飛的建議:用 Claude Code 或 Cursor 的 Composer 模式寫測試,因為它們能看到更多上下文。然後一定要人工 review 測試案例是否涵蓋了所有重要的業務邏輯。
安全與限制
AI 產生的測試可能給你虛假的安全感。100% 的測試覆蓋率不代表沒有 bug,因為 AI 可能漏掉重要的業務邏輯邊界。
AI 很容易產生「永遠通過」的測試。例如 assert 的條件寫錯(expect(result).toBeTruthy() 幾乎什麼都會通過),或 mock 太多導致測試完全不碰真實邏輯。Review 測試時要特別注意 assert 是否有意義。
不要讓 AI 同時修改程式碼和測試。如果測試失敗,AI 可能會修改測試讓它通過(而不是修改程式碼讓它正確)。在 Claude Code 的 CLAUDE.md 中可以加上:「測試失敗時,先分析是程式碼的 bug 還是測試的問題,不要直接修改測試讓它通過」。
AI 產生的測試資料可能包含真實資料的模式。如果你的 prompt 中描述了資料格式(例如「台灣身分證字號的驗證函式」),AI 可能會產生格式正確的身分證字號作為測試資料。這些號碼可能碰巧是真實的。建議使用明顯是假的測試資料。
知識檢測
讀完文章後,測試一下你對這個主題的理解。
常見問題
AI 可以達到多高的測試覆蓋率?
純函式的單元測試,AI 很容易達到 90% 以上的覆蓋率。但對於有複雜依賴的程式碼(資料庫操作、外部 API 呼叫),AI 產生的測試可能只能達到 40-60%,其餘需要手動補充。
用 AI 寫測試會比手寫快多少?
根據飛飛的觀察,單元測試大約快 3-5 倍。整合測試大約快 1.5-2 倍(因為需要大量手動調整)。E2E 測試幾乎沒有加速,因為 AI 不了解你的 UI 狀態。
AI 寫的測試可以用在 CI/CD 裡嗎?
可以,但建議先在本機跑通再放進 CI/CD。AI 產生的測試有時候有不穩定的問題(例如依賴特定時間或隨機值),這類 flaky test 放進 CI/CD 會造成困擾。
應該先寫程式碼還是先寫測試?
如果你採用 TDD(Test-Driven Development),可以先讓 AI 根據規格寫測試,再讓 AI 根據測試寫實作。這是最能避免循環驗證問題的做法,因為測試來自規格,實作來自測試,兩者的驗證基礎不同。
怎麼判斷 AI 寫的測試是否有效?
看三件事:一、assert 的條件是否具體(不是 toBeTruthy 或 toBeDefined 這種模糊斷言);二、是否覆蓋了邊界值和錯誤路徑;三、故意在程式碼中製造一個 bug,看測試是否能抓到(mutation testing 的概念)。
相關文章
參考資料
- Jest 官方文件
- Playwright 官方文件
- OWASP Testing Guide