---
title: AI 寫的 Test 怎麼信任 — QA 的 Review SOP 與信任邊界
date: 2026-06-23
category: ai-qa
description: AI 幫你生的 test case 跟自動化 code 到底能不能信？這篇給 QA 一套信任分級、7 種「綠燈但沒用」的假測試、5 步 review SOP、用 mutation testing 驗證測試真的有測、以及該守的紅線。
tags: [ai-generated-tests, test-review, trust-boundary, mutation-testing, qa-ai]
faq:
  - q: AI 生的 test 全綠就代表可信嗎？
    a: 不行。最危險的就是「綠燈但沒測到東西」的假測試 — 斷言寫成恆真、mock 掉所有東西、只覆蓋 happy path。全綠只代表「沒報錯」、不代表「測對了」。要用 mutation testing 反向驗證測試真的會抓 bug。
  - q: 哪些 test 可以放心交給 AI、哪些不行？
    a: 可交給 AI:boilerplate、setup/teardown、明確規格的 happy path、重複結構的 case。不可全信:斷言的「預期值」、domain 規則（稅法/金融/健保）、安全相關、邊界與異常的覆蓋判斷。AI 出草稿、預期值跟覆蓋一定人工把關。
  - q: 怎麼快速判斷一段 AI 測試碼有沒有用？
    a: 三個 30 秒檢查:1) 故意改壞被測 code、test 會不會紅（不紅 = 假測試）；2) 斷言是不是只驗 mock 被呼叫、沒驗真實結果；3) 預期值是 AI 算的還是規格來的。三關過了才算可信。
  - q: 團隊要不要禁止 AI 寫 test？
    a: 不要禁、要立規矩。禁了大家偷用更糟。立 PR 規範:AI 生的 test 要標註、預期值需附規格來源、合併前跑 mutation 抽查。把信任流程化、比靠個人自律可靠。
---

# AI 寫的 Test 怎麼信任

「Claude 幫我生了 30 個 test、全綠、可以合了吧？」**這是 QA 最該警覺的一句話**。AI 生測試最大的風險不是它寫不出來、是它寫出一堆**綠燈但根本沒測到東西**的假測試、給你虛假的安全感。這篇給你一套信任邊界 + review SOP。

## 核心問題：綠 ≠ 有測到

```mermaid
flowchart LR
    A[AI 生 30 個 test] --> B[全部綠燈 ✅]
    B --> C{綠燈代表什麼?}
    C --> D["只代表:沒拋錯"]
    C -.以為.-> E["以為:有測到 bug"]
    D --> F[改壞 code<br>test 還是綠 ❌]

    style B fill:#10b981,color:#fff
    style E fill:#ef4444,color:#fff
    style F fill:#ef4444,color:#fff
```

傳統人寫 test、至少寫的時候腦中有「我要抓什麼」。AI 生 test 是**從語法層補出一個看起來合理的 test**、不保證背後有測試意圖。綠燈只證明「程式沒爆」、不證明「斷言有意義」。

## 7 種「綠燈但沒用」的假測試

```mermaid
mindmap
  root((AI 假測試))
    恆真斷言
      assert true
      expect 1 == 1
    過度 mock
      mock 掉被測對象本身
      測到的是 mock 不是 code
    驗呼叫不驗結果
      只 assert 函式被呼叫
      沒驗回傳值對不對
    幻覺預期值
      AI 自己編的 expected
      沒對規格
    只測 happy path
      0 個邊界
      0 個異常
    快照記錯誤
      snapshot 記下當前 bug
      把錯的當基準
    重複洗數量
      10 個 case 測同一條路
      覆蓋假象
```

| # | 假測試 | 怎麼看出來 |
|---|--------|-----------|
| 1 | **恆真斷言** | `expect(result).toBeDefined()` 永遠過、沒驗內容 |
| 2 | **過度 mock** | 連被測 function 都 mock 掉、測的是假貨 |
| 3 | **驗呼叫不驗結果** | 只 `toHaveBeenCalled()`、沒比對回傳值 |
| 4 | **幻覺預期值** | expected 是 AI 算的、不是規格給的 |
| 5 | **只有 happy path** | 0 邊界、0 異常、0 安全 |
| 6 | **快照記錯誤** | snapshot 把當前（含 bug）行為當基準 |
| 7 | **重複洗數量** | 10 個 case 其實測同一條路徑 |

## 信任分級 — 哪些能交給 AI

```mermaid
flowchart TB
    subgraph Green["🟢 可放心交給 AI"]
        G1[Boilerplate / setup]
        G2[明確規格的 happy path]
        G3[重複結構的 case]
        G4[測試命名 / 註解]
    end
    subgraph Yellow["🟡 AI 出草稿 + 人工把關"]
        Y1[邊界 / 異常覆蓋]
        Y2[斷言的預期值]
        Y3[mock 邊界該畫在哪]
    end
    subgraph Red["🔴 不可全交給 AI"]
        R1[Domain 規則 預期值]
        R2[安全相關斷言]
        R3[覆蓋是否足夠的判斷]
    end

    style Green fill:#10b981,color:#fff
    style Yellow fill:#f59e0b,color:#fff
    style Red fill:#ef4444,color:#fff
```

**一句話**：AI 可以幫你「打字」、不能幫你「決定預期值對不對」跟「覆蓋夠不夠」。預期值與覆蓋判斷、是 QA 不外包的核心。

## 5 步 Review SOP

```mermaid
flowchart LR
    S1["1.改壞測試<br>mutation 抽查"] --> S2["2.查斷言<br>驗結果非呼叫"]
    S2 --> S3["3.查預期值<br>對規格不對 AI"]
    S3 --> S4["4.查覆蓋<br>邊界/異常/安全"]
    S4 --> S5["5.查 mock<br>沒 mock 掉被測對象"]

    style S1 fill:#a855f7,color:#fff
    style S3 fill:#a855f7,color:#fff
    style S5 fill:#a855f7,color:#fff
```

### Step 1 — 改壞 code，看 test 會不會紅

最快的真假測試判別法。故意把被測邏輯改錯一行：

```python
# 被測 code（故意改壞）
def calc_overtime(hours):
    # return hours * 1.33   # 原本對的
    return hours * 1.0      # 故意改錯

# 如果 AI 生的 test 還是綠 → 這 test 是假的
```

合併前對關鍵模組做這件事、或直接上 **mutation testing** 工具自動跑（見下）。

### Step 2 — 斷言驗「結果」不是驗「呼叫」

```javascript
// ❌ AI 常生這種:只驗有被叫
expect(saveUser).toHaveBeenCalled();

// ✅ 要的是驗結果對不對
expect(result.status).toBe('active');
expect(result.email).toBe('qa@test.com');
```

### Step 3 — 預期值必須來自規格、不是 AI 腦補

這是最隱蔽的雷。AI 會「算」一個預期值填進去、看起來合理但跟業務規則不符。

```
Prompt 改法:
「產生 test、但 expected 值欄位先留 TODO、
 並在旁邊註明這個值該依據哪條規格決定。
 不要自己猜數字。」
```

預期值你親自對規格填、或 review 時逐一對。**金融 / 稅務 / 健保類的數字、100% 人工核對**。

### Step 4 — 覆蓋盤點

要求 AI 自己列覆蓋矩陣、你檢查缺口：

```
Prompt:
「用表格列出你產的 test 覆蓋哪些情境:
 happy / 邊界 / 異常 / 安全 / 併發。
 哪些情境『沒』被覆蓋？老實列出來。」
```

AI 常只給 happy path。缺的邊界 / 異常自己補。

### Step 5 — 檢查 mock 邊界

```javascript
// ❌ 把被測對象本身 mock 掉 → 測了個寂寞
jest.mock('./userService');  // 然後又在測 userService

// ✅ 只 mock 外部邊界(DB / API / 時間)
jest.mock('./db');
```

原則：**只 mock external boundary、不 mock 被測主體**。

## 用 Mutation Testing 反向驗證

Review 靠肉眼會累、用 mutation testing 自動驗「測試真的會抓 bug」：

```bash
# JS/TS
npx stryker run

# Python
mutmut run

# Java
mvn org.pitest:pitest-maven:mutationCoverage
```

```mermaid
flowchart LR
    M[Mutation Tool] --> C[自動改壞 code 100 處]
    C --> T[每改一處跑一次 test]
    T --> R{test 有紅嗎?}
    R -->|紅| K["killed ✅ 好測試"]
    R -->|綠| S["survived ❌ 假測試"]

    style K fill:#10b981,color:#fff
    style S fill:#ef4444,color:#fff
```

**Mutation score 比 coverage 誠實**。Line coverage 90% 但 mutation score 30%、代表 test 跑過了 code 卻抓不到 bug — 正是 AI 假測試的典型特徵。CI 設 mutation score 門檻、AI 假測試直接被擋。

## CI 守門設定

```yaml
# AI 生的 test 進 CI 前的守門
gates:
  - 改壞抽查（關鍵模組 mutation testing）
  - 禁恆真斷言（lint 規則 no-tautology）
  - 覆蓋矩陣需附在 PR description
  - 預期值需標規格來源
  - 禁 mock 被測主體（review checklist）
```

## 反模式

```mermaid
flowchart TD
    Anti[AI 測試反模式] --> A1["全綠就合、不抽查"]
    Anti --> A2["相信 AI 算的預期值"]
    Anti --> A3["只看 coverage %、不看 mutation"]
    Anti --> A4["AI 生多少 case 就交多少"]
    Anti --> A5["mock 到測不到真實邏輯"]
    Anti --> A6["snapshot 全收、含 bug 一起記"]

    style A1 fill:#ef4444,color:#fff
    style A2 fill:#ef4444,color:#fff
    style A3 fill:#ef4444,color:#fff
    style A4 fill:#ef4444,color:#fff
    style A5 fill:#ef4444,color:#fff
    style A6 fill:#ef4444,color:#fff
```

## 給 QA 的 5 句

1. **綠燈只代表沒爆、不代表有測到**
2. **改壞 code 不紅的 test 是假的**
3. **預期值對規格、不對 AI**
4. **Mutation score 比 coverage 誠實**
5. **AI 幫打字、覆蓋判斷你自己扛**

## 最後

AI 生 test 能省一半打字時間、但**省不掉 QA 的判斷**。把這套 review SOP 變成 PR checklist、再加一道 mutation testing 守門、你就能放心讓 AI 加速、又不會被假測試騙。信任不是「相信 AI」、是「建立可驗證的信任流程」。

延伸：
- [AI 生成 Code 怎麼測](/ai-qa/testing-ai-generated-code.html)
- [用 LLM 生 Test Case](/ai-qa/llm-test-case-generation.html)
- [QA 的 AI Workflow Daily Routine](/ai-qa/qa-ai-daily-workflow.html)
- [Self-healing Tests with LLM](/ai-qa/self-healing-tests.html)
