🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx skills add https://skills.sh/affaan-m/everything-claude-code/ai-regression-testing

💡 提示:需要 Node.js 和 NPM

AI 回归测试 (AI Regression Testing)

专为 AI 辅助开发设计的测试模式,在这种模式下,同一个模型既编写代码又审查代码——从而产生只有自动化测试才能发现的系统性盲点。

何时激活 (When to Activate)

  • AI 智能体(Claude Code、Cursor、Codex)修改了 API 路由或后端逻辑
  • 发现并修复了一个 Bug — 需要防止再次引入
  • 项目有沙盒/模拟模式,可用于无数据库测试
  • 在代码更改后运行 /bug-check 或类似的审查命令
  • 存在多个代码路径(沙盒与生产环境、功能标志等)

核心问题 (The Core Problem)

当 AI 编写代码然后审查自己的工作时,它会在两个步骤中带入相同的假设。这会产生一个可预测的失败模式:

AI 编写修复 → AI 审查修复 → AI 说“看起来正确” → Bug 仍然存在

真实世界示例 (Real-world example)(在生产环境中观察到):

修复 1: 在 API 响应中添加 notification_settings
  → 忘记将其添加到 SELECT 查询中
  → AI 审查并遗漏了(同样的盲点)

修复 2: 将其添加到 SELECT 查询中
  → TypeScript 构建错误(列不在生成的类型中)
  → AI 审查了修复 1,但没有发现 SELECT 问题

修复 3: 改为 SELECT *
  → 修复了生产路径,忘记了沙盒路径
  → AI 审查并再次遗漏(第 4 次)

修复 4: 测试在第一次运行时立即捕获 ✅

模式:沙盒/生产路径不一致 (sandbox/production path inconsistency) 是 #1 的 AI 引入的回归问题。

沙盒模式 API 测试 (Sandbox-Mode API Testing)

大多数具有 AI 友好架构的项目都有沙盒/模拟模式。这是快速、无数据库 API 测试的关键。

设置(Vitest + Next.js App Router)(Setup – 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
// 强制沙盒模式 — 无需数据库 (Force sandbox mode — no database needed)
process.env.SANDBOX_MODE = "true";
process.env.NEXT_PUBLIC_SUPABASE_URL = "";
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = "";

Next.js API 路由的测试辅助函数 (Test Helper for Next.js API Routes)

// __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 };
}

编写回归测试 (Writing Regression Tests)

关键原则:为发现的 Bug 编写测试,而不是为正常工作的代码编写测试

// __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";

// 定义契约 — 响应中必须包含的字段 (Define the contract — what fields MUST be in the response)
const REQUIRED_FIELDS = [
  "id",
  "email",
  "full_name",
  "phone",
  "role",
  "created_at",
  "avatar_url",
  "notification_settings",  // ← 在发现缺失 Bug 后添加 (Added after bug found it missing)
];

describe("GET /api/user/profile", () => {
  it("返回所有必需字段 (returns all required fields)", 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);
    }
  });

  // 回归测试 — 这个确切的 Bug 被 AI 引入了 4 次 (Regression test — this exact bug was introduced by AI 4 times)
  it("notification_settings 不是未定义 (BUG-R1 回归) (notification_settings is not undefined - BUG-R1 regression)", 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);
  });
});

测试沙盒/生产环境一致性 (Testing Sandbox/Production Parity)

最常见的 AI 回归:修复生产路径但忘记沙盒路径(反之亦然)。

// 测试沙盒响应是否符合预期契约 (Test that sandbox responses match the expected contract)
describe("GET /api/user/messages (会话列表 - conversation list)", () => {
  it("在沙盒模式下包含 partner_name (includes partner_name in sandbox mode)", async () => {
    const req = createTestRequest("/api/user/messages", {
      sandboxUserId: "user-001",
    });
    const res = await GET(req);
    const { json } = await parseResponse(res);

    // 这捕获了一个 Bug,其中 partner_name 被添加到了
    // 生产路径但未添加到沙盒路径 (This caught a bug where partner_name was added to production path but not sandbox path)
    if (json.data.length > 0) {
      for (const conv of json.data) {
        expect("partner_name" in conv).toBe(true);
      }
    }
  });
});

将测试集成到 Bug 检查工作流中 (Integrating Tests into Bug-Check Workflow)

自定义命令定义 (Custom Command Definition)

<!-- .claude/commands/bug-check.md -->
# Bug 检查 (Bug Check)

## 步骤 1:自动化测试(强制,不可跳过)(Step 1: Automated Tests - mandatory, cannot skip)

在代码审查之前,首先运行这些命令:
(Run these commands FIRST before any code review:)

    npm run test       # Vitest 测试套件 (Vitest test suite)
    npm run build      # TypeScript 类型检查 + 构建 (TypeScript type check + build)

- 如果测试失败 → 报告为最高优先级 Bug (If tests fail → report as highest priority bug)
- 如果构建失败 → 报告类型错误为最高优先级 (If build fails → report type errors as highest priority)
- 仅当两者都通过时才进入步骤 2 (Only proceed to Step 2 if both pass)

## 步骤 2:代码审查(AI 审查)(Step 2: Code Review - AI review)

1. 沙盒/生产路径一致性 (Sandbox / production path consistency)
2. API 响应形状与前端的期望一致 (API response shape matches frontend expectations)
3. SELECT 子句完整性 (SELECT clause completeness)
4. 带回滚的错误处理 (Error handling with rollback)
5. 乐观更新的竞态条件 (Optimistic update race conditions)

## 步骤 3:对于每个修复的 Bug,提出一个回归测试 (Step 3: For each bug fixed, propose a regression test)

工作流 (The Workflow)

用户 (User): "バグチェックして" (或 "/bug-check")
  ├─ 步骤 1: npm run test
  │   ├─ 失败 (FAIL) → 机械地发现 Bug(无需 AI 判断)(Bug found mechanically - no AI judgment needed)
  │   └─ 通过 (PASS) → 继续 (Continue)
  ├─ 步骤 2: npm run build
  │   ├─ 失败 (FAIL) → 机械地发现类型错误 (Type error found mechanically)
  │   └─ 通过 (PASS) → 继续 (Continue)
  ├─ 步骤 3: AI 代码审查(记住已知的盲点)(AI code review - with known blind spots in mind)
  │   └─ 报告发现 (Findings reported)
  └─ 步骤 4: 对于每个修复,编写回归测试 (For each fix, write a regression test)
      └─ 下次 bug-check 会在修复被破坏时捕获 (Next bug-check catches if fix breaks)

常见的 AI 回归模式 (Common AI Regression Patterns)

模式 1:沙盒/生产路径不匹配 (Pattern 1: Sandbox/Production Path Mismatch)

频率 (Frequency):最常见(在 4 次回归中观察到 3 次)

// ❌ AI 仅将字段添加到生产路径 (AI adds field to production path only)
if (isSandboxMode()) {
  return { data: { id, email, name } };  // 缺少新字段 (Missing new field)
}
// 生产路径 (Production path)
return { data: { id, email, name, notification_settings } };

// ✅ 两条路径必须返回相同的形状 (Both paths must return the same shape)
if (isSandboxMode()) {
  return { data: { id, email, name, notification_settings: null } };
}
return { data: { id, email, name, notification_settings } };

捕获它的测试 (Test to catch it)

it("沙盒和生产环境返回相同字段 (sandbox and production return same fields)", async () => {
  // 在测试环境中,沙盒模式被强制开启 (In test env, sandbox mode is forced ON)
  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 子句遗漏 (Pattern 2: SELECT Clause Omission)

频率 (Frequency):使用 Supabase/Prisma 添加新列时很常见

// ❌ 新列添加到响应但未添加到 SELECT
const { data } = await supabase
  .from("users")
  .select("id, email, name")  // notification_settings 不在这里 (notification_settings not here)
  .single();

return { data: { ...data, notification_settings: data.notification_settings } };
// → notification_settings 始终是未定义 (is always undefined)

// ✅ 使用 SELECT * 或显式包含新列 (Use SELECT * or explicitly include new columns)
const { data } = await supabase
  .from("users")
  .select("*")
  .single();

模式 3:错误状态泄漏 (Pattern 3: Error State Leakage)

频率 (Frequency):中等 — 在向现有组件添加错误处理时

// ❌ 设置了错误状态,但旧数据未清除 (Error state set but old data not cleared)
catch (err) {
  setError("加载失败 (Failed to load)");
  // reservations 仍然显示之前选项卡的数据!(reservations still shows data from previous tab!)
}

// ✅ 在错误时清除相关状态 (Clear related state on error)
catch (err) {
  setReservations([]);  // 清除过时数据 (Clear stale data)
  setError("加载失败 (Failed to load)");
}

模式 4:没有正确回滚的乐观更新 (Pattern 4: Optimistic Update Without Proper Rollback)

// ❌ 失败时没有回滚 (No rollback on failure)
const handleRemove = async (id: string) => {
  setItems(prev => prev.filter(i => i.id !== id));
  await fetch(`/api/items/${id}`, { method: "DELETE" });
  // 如果 API 失败,项目已从 UI 中消失,但仍存在于数据库中 (If API fails, item is gone from UI but still in DB)
};

// ✅ 捕获先前状态,并在失败时回滚 (Capture previous state and rollback on failure)
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 错误 (API error)");
  } catch {
    setItems(prevItems);  // 回滚 (Rollback)
    alert("削除に失敗しました");
  }
};

策略:在发现 Bug 的地方进行测试 (Strategy: Test Where Bugs Were Found)

不要追求 100% 的覆盖率。相反:

在 /api/user/profile 中发现 Bug     → 为 profile API 编写测试
在 /api/user/messages 中发现 Bug    → 为 messages API 编写测试
在 /api/user/favorites 中发现 Bug   → 为 favorites API 编写测试
在 /api/user/notifications 中没有 Bug → (暂时)不编写测试

为什么这对 AI 开发有效 (Why this works with AI development):

  1. AI 倾向于重复犯同类型的错误 (AI tends to make the same category of mistake repeatedly)
  2. Bug 聚集在复杂区域(认证、多路径逻辑、状态管理)(Bugs cluster in complex areas – auth, multi-path logic, state management)
  3. 一旦经过测试,那个确切的回归就不会再发生 (Once tested, that exact regression cannot happen again)
  4. 测试数量随着 Bug 修复有机增长——没有浪费精力 (Test count grows organically with bug fixes — no wasted effort)

快速参考 (Quick Reference)

AI 回归模式 (AI Regression Pattern) 测试策略 (Test Strategy) 优先级 (Priority)
沙盒/生产环境不匹配 (Sandbox/production mismatch) 在沙盒模式下断言相同的响应形状 (Assert same response shape in sandbox mode) 🔴 高 (High)
SELECT 子句遗漏 (SELECT clause omission) 断言响应中包含所有必需字段 (Assert all required fields in response) 🔴 高 (High)
错误状态泄漏 (Error state leakage) 断言错误时状态被清除 (Assert state cleanup on error) 🟡 中 (Medium)
缺少回滚 (Missing rollback) 断言 API 失败时状态恢复 (Assert state restored on API failure) 🟡 中 (Medium)
类型转换掩盖 null (Type cast masking null) 断言字段不是未定义 (Assert field is not undefined) 🟡 中 (Medium)

应该做 / 不应该做 (DO / DON’T)

应该做 (DO):

  • 发现 Bug 后立即编写测试(如果可能,在修复之前)(Write tests immediately after finding a bug – before fixing it if possible)
  • 测试 API 响应形状,而不是实现 (Test the API response shape, not the implementation)
  • 将测试作为每次 bug-check 的第一步运行 (Run tests as the first step of every bug-check)
  • 保持测试快速(使用沙盒模式总时间 < 1 秒)(Keep tests fast – < 1 second total with sandbox mode)
  • 以它们防止的 Bug 命名测试(例如,“BUG-R1 回归”)(Name tests after the bug they prevent – e.g., “BUG-R1 regression”)

不应该做 (DON’T):

  • 为从未出现过 Bug 的代码编写测试 (Write tests for code that has never had a bug)
  • 相信 AI 自审可以替代自动化测试 (Trust AI self-review as a substitute for automated tests)
  • 因为“只是模拟数据”而跳过沙盒路径测试 (Skip sandbox path testing because “it’s just mock data”)
  • 当单元测试足够时编写集成测试 (Write integration tests when unit tests suffice)
  • 追求覆盖率百分比 — 追求回归预防 (Aim for coverage percentage — aim for regression prevention)

📄 原始文档

完整文档(英文):

https://skills.sh/affaan-m/everything-claude-code/ai-regression-testing

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。