ai-regression-testing

ai-regression-testing

熱門

AI輔助開發的回歸測試策略。無需資料庫依賴的沙箱模式API測試、自動化錯誤檢查工作流程,以及捕捉AI盲點(同一模型撰寫與審查程式碼)的模式。

23萬星標
3.5萬分支
更新於 2026/7/17
SKILL.md
readonlyread-only
name
ai-regression-testing
description

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code.

AI 回歸測試

專為 AI 輔助開發設計的測試模式,當同一模型撰寫與審查程式碼時,會產生系統性盲點,唯有自動化測試才能捕捉。

何時啟用

  • AI 代理(Claude Code、Cursor、Codex)已修改 API 路由或後端邏輯
  • 發現並修復了錯誤——需要防止再次引入
  • 專案有沙箱/模擬模式可用於無資料庫測試
  • 在程式碼變更後執行 /bug-check 或類似的審查指令
  • 存在多條程式碼路徑(沙箱 vs 正式環境、功能開關等)

核心問題

當 AI 撰寫程式碼後自行審查,它會將相同的假設帶入兩個步驟。這會產生可預測的失敗模式:

AI 寫修正 → AI 審查修正 → AI 說「看起來正確」→ 錯誤仍然存在

真實案例(在正式環境中觀察到):

修正 1:在 API 回應中加入 notification_settings
  → 忘記加入 SELECT 查詢
  → AI 審查時遺漏(相同盲點)

修正 2:加入 SELECT 查詢
  → TypeScript 建置錯誤(欄位不在產生的型別中)
  → AI 審查修正 1 但未發現 SELECT 問題

修正 3:改為 SELECT *
  → 修正了正式環境路徑,但忘記沙箱路徑
  → AI 再次審查遺漏(第 4 次發生)

修正 4:測試在第一次執行時立即捕捉到 PASS:

模式:沙箱/正式環境路徑不一致是 AI 引入回歸的第一大原因。

沙箱模式 API 測試

大多數具有 AI 友善架構的專案都有沙箱/模擬模式。這是快速、無資料庫 API 測試的關鍵。

設定(Vitest + Next.js App Router)

// vitest.config.ts
import { defineConfig } from "vitest/config";
import path from "path";

export default defineConfig({
  test: {
    environment: "node",
    globals: true,
    include: ["__tests__/**/*.test.ts"],
    setupFiles: ["__tests__/setup.ts"],
  },
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "."),
    },
  },
});
// __tests__/setup.ts
// 強制沙箱模式——無需資料庫
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";

Next.js API 路由測試輔助函式

// __tests__/helpers.ts
import { NextRequest } from "next/server";

export function createTestRequest(
  url: string,
  options?: {
    method?: string;
    body?: Record<string, unknown>;
    headers?: Record<string, string>;
    sandboxUserId?: string;
  },
): NextRequest {
  const { method = "GET", body, headers = {}, sandboxUserId } = options || {};
  const fullUrl = url.startsWith("http") ? url : `http://localhost:3000${url}`;
  const reqHeaders: Record<string, string> = { ...headers };

  if (sandboxUserId) {
    reqHeaders["x-sandbox-user-id"] = sandboxUserId;
  }

  const init: { method: string; headers: Record<string, string>; body?: string } = {
    method,
    headers: reqHeaders,
  };

  if (body) {
    init.body = JSON.stringify(body);
    reqHeaders["content-type"] = "application/json";
  }

  return new NextRequest(fullUrl, init);
}

export async function parseResponse(response: Response) {
  const json = await response.json();
  return { status: response.status, json };
}

撰寫回歸測試

關鍵原則:為發現的錯誤撰寫測試,而非為正常運作的程式碼

// __tests__/api/user/profile.test.ts
import { describe, it, expect } from "vitest";
import { createTestRequest, parseResponse } from "../../helpers";
import { GET, PATCH } from "@/app/api/user/profile/route";

// 定義合約——回應中必須包含哪些欄位
const REQUIRED_FIELDS = [
  "id",
  "email",
  "full_name",
  "phone",
  "role",
  "created_at",
  "avatar_url",
  "notification_settings",  // ← 發現遺漏錯誤後加入
];

describe("GET /api/user/profile", () => {
  it("回傳所有必要欄位", async () => {
    const req = createTestRequest("/api/user/profile");
    const res = await GET(req);
    const { status, json } = await parseResponse(res);

    expect(status).toBe(200);
    for (const field of REQUIRED_FIELDS) {
      expect(json.data).toHaveProperty(field);
    }
  });

  // 回歸測試——這個確切錯誤被 AI 引入了 4 次
  it("notification_settings 不為 undefined(BUG-R1 回歸)", async () => {
    const req = createTestRequest("/api/user/profile");
    const res = await GET(req);
    const { json } = await parseResponse(res);

    expect("notification_settings" in json.data).toBe(true);
    const ns = json.data.notification_settings;
    expect(ns === null || typeof ns === "object").toBe(true);
  });
});

測試沙箱/正式環境一致性

最常見的 AI 回歸:修正正式環境路徑但忘記沙箱路徑(或相反)。

// 測試沙箱回應是否符合預期合約
describe("GET /api/user/messages(對話列表)", () => {
  it("在沙箱模式中包含 partner_name", async () => {
    const req = createTestRequest("/api/user/messages", {
      sandboxUserId: "user-001",
    });
    const res = await GET(req);
    const { json } = await parseResponse(res);

    // 這捕捉到一個錯誤:partner_name 被加入正式環境路徑但未加入沙箱路徑
    if (json.data.length > 0) {
      for (const conv of json.data) {
        expect("partner_name" in conv).toBe(true);
      }
    }
  });
});

將測試整合到錯誤檢查工作流程

自訂指令定義

<!-- .claude/commands/bug-check.md -->
# 錯誤檢查

## 步驟 1:自動化測試(強制,不可跳過)

在進行任何程式碼審查之前,先執行以下指令:

    npm run test       # Vitest 測試套件
    npm run build      # TypeScript 型別檢查 + 建置

- 如果測試失敗 → 回報為最高優先級錯誤
- 如果建置失敗 → 回報型別錯誤為最高優先級
- 兩者皆通過後才進入步驟 2

## 步驟 2:程式碼審查(AI 審查)

1. 沙箱/正式環境路徑一致性
2. API 回應形狀是否符合前端預期
3. SELECT 子句完整性
4. 含回滾的錯誤處理
5. 樂觀更新的競態條件

## 步驟 3:針對每個修正的錯誤,提出一個回歸測試

工作流程

使用者:「バグチェックして」(或 "/bug-check")
  │
  ├─ 步驟 1:npm run test
  │   ├─ 失敗 → 機械化發現錯誤(無需 AI 判斷)
  │   └─ 通過 → 繼續
  │
  ├─ 步驟 2:npm run build
  │   ├─ 失敗 → 機械化發現型別錯誤
  │   └─ 通過 → 繼續
  │
  ├─ 步驟 3:AI 程式碼審查(留意已知盲點)
  │   └─ 回報發現
  │
  └─ 步驟 4:針對每個修正撰寫回歸測試
      └─ 下次錯誤檢查時若修正被破壞則會捕捉

常見 AI 回歸模式

模式 1:沙箱/正式環境路徑不一致

頻率:最常見(在 3/4 的回歸中觀察到)

// 失敗:AI 僅在正式環境路徑加入欄位
if (isSandboxMode()) {
  return { data: { id, email, name } };  // 缺少新欄位
}
// 正式環境路徑
return { data: { id, email, name, notification_settings } };

// 通過:兩個路徑必須回傳相同形狀
if (isSandboxMode()) {
  return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };

捕捉此模式的測試

it("沙箱和正式環境回傳相同欄位", async () => {
  // 在測試環境中,沙箱模式被強制開啟
  const res = await GET(createTestRequest("/api/user/profile"));
  const { json } = await parseResponse(res);

  for (const field of REQUIRED_FIELDS) {
    expect(json.data).toHaveProperty(field);
  }
});

模式 2:SELECT 子句遺漏

頻率:使用 Supabase/Prisma 新增欄位時常見

// 失敗:新欄位加入回應但未加入 SELECT
const { data } = await supabase
  .from("users")
  .select("id, email, name")  // notification_settings 不在這裡
  .single();

return { data: { ...data, notification_settings: data.notification_settings } };
// → notification_settings 永遠是 undefined

// 通過:使用 SELECT * 或明確包含新欄位
const { data } = await supabase
  .from("users")
  .select("*")
  .single();

模式 3:錯誤狀態洩漏

頻率:中等——在現有元件中加入錯誤處理時

// 失敗:設定了錯誤狀態但未清除舊資料
catch (err) {
  setError("載入失敗");
  // reservations 仍顯示前一個分頁的資料!
}

// 通過:錯誤時清除相關狀態
catch (err) {
  setReservations([]);  // 清除過時資料
  setError("載入失敗");
}

模式 4:樂觀更新缺少適當回滾

// 失敗:失敗時無回滾
const handleRemove = async (id: string) => {
  setItems(prev => prev.filter(i => i.id !== id));
  await fetch(`/api/items/${id}`, { method: "DELETE" });
  // 如果 API 失敗,項目從 UI 消失但仍在資料庫中
};

// 通過:捕捉先前狀態並在失敗時回滾
const handleRemove = async (id: string) => {
  const prevItems = [...items];
  setItems(prev => prev.filter(i => i.id !== id));
  try {
    const res = await fetch(`/api/items/${id}`, { method: "DELETE" });
    if (!res.ok) throw new Error("API 錯誤");
  } catch {
    setItems(prevItems);  // 回滾
    alert("刪除失敗");
  }
};

策略:在發現錯誤的地方進行測試

不要追求 100% 覆蓋率。而是:

在 /api/user/profile 發現錯誤 → 為 profile API 撰寫測試
在 /api/user/messages 發現錯誤 → 為 messages API 撰寫測試
在 /api/user/favorites 發現錯誤 → 為 favorites API 撰寫測試
在 /api/user/notifications 無錯誤 → 不撰寫測試(暫時)

為什麼這在 AI 開發中有效:

  1. AI 傾向於重複犯下相同類型的錯誤
  2. 錯誤集中在複雜區域(認證、多路徑邏輯、狀態管理)
  3. 一旦測試,該確切回歸不可能再次發生
  4. 測試數量隨著錯誤修正自然增長——無浪費

快速參考

AI 回歸模式 測試策略 優先級
沙箱/正式環境不一致 在沙箱模式中斷言相同回應形狀
SELECT 子句遺漏 在回應中斷言所有必要欄位
錯誤狀態洩漏 在錯誤時斷言狀態清理
缺少回滾 在 API 失敗時斷言狀態恢復
型別轉換掩蓋 null 斷言欄位不為 undefined

要點 / 避免事項

要點:

  • 發現錯誤後立即撰寫測試(如果可能,在修正之前)
  • 測試 API 回應形狀,而非實作
  • 每次錯誤檢查的第一步執行測試
  • 保持測試快速(使用沙箱模式總計 < 1 秒)
  • 以測試防止的錯誤命名(例如「BUG-R1 回歸」)

避免事項:

  • 為從未發生錯誤的程式碼撰寫測試
  • 信任 AI 自我審查作為自動化測試的替代
  • 因為「只是模擬資料」而跳過沙箱路徑測試
  • 在單元測試足夠時撰寫整合測試
  • 追求覆蓋率百分比——目標是防止回歸