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 開發中有效:
- AI 傾向於重複犯下相同類型的錯誤
- 錯誤集中在複雜區域(認證、多路徑邏輯、狀態管理)
- 一旦測試,該確切回歸不可能再次發生
- 測試數量隨著錯誤修正自然增長——無浪費
快速參考
| AI 回歸模式 | 測試策略 | 優先級 |
|---|---|---|
| 沙箱/正式環境不一致 | 在沙箱模式中斷言相同回應形狀 | 高 |
| SELECT 子句遺漏 | 在回應中斷言所有必要欄位 | 高 |
| 錯誤狀態洩漏 | 在錯誤時斷言狀態清理 | 中 |
| 缺少回滾 | 在 API 失敗時斷言狀態恢復 | 中 |
| 型別轉換掩蓋 null | 斷言欄位不為 undefined | 中 |
要點 / 避免事項
要點:
- 發現錯誤後立即撰寫測試(如果可能,在修正之前)
- 測試 API 回應形狀,而非實作
- 每次錯誤檢查的第一步執行測試
- 保持測試快速(使用沙箱模式總計 < 1 秒)
- 以測試防止的錯誤命名(例如「BUG-R1 回歸」)
避免事項:
- 為從未發生錯誤的程式碼撰寫測試
- 信任 AI 自我審查作為自動化測試的替代
- 因為「只是模擬資料」而跳過沙箱路徑測試
- 在單元測試足夠時撰寫整合測試
- 追求覆蓋率百分比——目標是防止回歸






