← 返回首页

testing-strategies@1.0.0

测试策略指南:单元测试、集成测试、E2E 测试、Mock 策略和测试金字塔

CLI 安装指南

通过 SkillSPAI CLI 一键安装到你的 AI 编码工具:

1. 安装 CLI(如尚未安装)

npm install -g skillspai

2. 安装此 Skill 到目标平台

# Codex CLI
skillspai install testing-strategies --target codex

# Claude Code
skillspai install testing-strategies --target claude

# Cursor
skillspai install testing-strategies --target cursor

# Windsurf
skillspai install testing-strategies --target windsurf

# OpenCode
skillspai install testing-strategies --target opencode

3. 或指定版本安装

skillspai install testing-strategies@1.0.0 --target claude

Skill 内容

# 测试策略指南

## 测试金字塔

```
        /  E2E  \           少量,验证核心用户流程
       / 集成测试 \          中量,验证模块协作
      /  单元测试   \        大量,验证独立函数/类
```

- 单元测试:70%,快速、稳定、覆盖边界条件
- 集成测试:20%,验证模块间交互、数据库操作
- E2E 测试:10%,验证关键用户流程(登录、支付)

## 单元测试

### 原则
- 一个测试只验证一个行为
- 测试名称描述行为:`should return 404 when user not found`
- 测试独立,不依赖执行顺序
- 测试快速(单个 < 100ms)

### 结构(AAA 模式)
```js
test("should calculate total with discount", () => {
  // Arrange - 准备
  const items = [{ price: 100, qty: 2 }, { price: 50, qty: 1 }];
  const discount = 0.1;

  // Act - 执行
  const total = calculateTotal(items, discount);

  // Assert - 断言
  expect(total).toBe(225); // (200 + 50) * 0.9
});
```

### 边界条件检查清单
- 空输入(null、undefined、空数组、空字符串)
- 极值(0、-1、Number.MAX_SAFE_INTEGER)
- 类型边界(字符串 vs 数字、整数 vs 浮点数)
- 特殊字符(HTML 标签、SQL 特殊字符、emoji)

## 集成测试

### 数据库测试
```js
// 使用真实数据库(SQLite 内存库或独立测试库)
beforeEach(async () => {
  db = await createTestDb();
  await db.migrate();
});

afterEach(async () => {
  await db.close();
});

test("should create and retrieve user", async () => {
  await db.insert("users", { name: "test" });
  const user = await db.findById("users", 1);
  expect(user.name).toBe("test");
});
```

### API 测试
```js
// 使用 supertest 或 httpx
test("POST /api/users creates user", async () => {
  const res = await request(app)
    .post("/api/users")
    .send({ name: "test", email: "test@example.com" })
    .expect(201);

  expect(res.body.data.name).toBe("test");
});

test("POST /api/users returns 400 for invalid email", async () => {
  await request(app)
    .post("/api/users")
    .send({ name: "test", email: "invalid" })
    .expect(400);
});
```

## Mock 策略

### 何时 Mock
- 外部 API 调用(支付、短信、邮件)
- 文件系统操作(可使用 memfs)
- 时间依赖(`vi.useFakeTimers()`)
- 随机数(固定 seed)

### 何时不 Mock
- 业务逻辑函数(直接测试真实实现)
- 数据库操作(用真实测试库)
- 内部工具函数

### Mock 示例
```js
// ❌ 过度 Mock
test("getUser", () => {
  mockDb.query.mockReturnValue({ id: 1, name: "test" });
  // 这只是在测试 mock,没有测试真实逻辑
});

// ✅ Mock 外部依赖,测试真实逻辑
test("sendWelcomeEmail", async () => {
  const mockSendMail = vi.fn().mockResolvedValue(true);
  const service = new EmailService({ sendMail: mockSendMail });

  await service.sendWelcome("test@example.com");

  expect(mockSendMail).toHaveBeenCalledWith({
    to: "test@example.com",
    subject: expect.stringContaining("Welcome"),
  });
});
```

## 测试覆盖率

### 目标
- 核心业务逻辑:> 90%
- API 路由层:> 80%
- 工具函数:> 90%
- 整体项目:> 70%

### 不要追求 100%
- 配置文件、类型定义不需要测试
- 第三方库封装只需少量集成测试
- UI 组件的像素级还原不需要测试

## 测试工具推荐

### JavaScript/TypeScript
- 单元测试:Vitest(推荐)或 Jest
- API 测试:supertest
- E2E 测试:Playwright
- Mock:vi.fn()(Vitest 内置)

### Python
- 单元测试:pytest
- API 测试:httpx + pytest-asyncio
- Mock:unittest.mock / pytest-mock
- 覆盖率:pytest-cov

## 常见错误

- ❌ 测试通过但代码有 bug → 检查是否覆盖了边界条件
- ❌ 测试依赖执行顺序 → 每个测试独立 setup/teardown
- ❌ Mock 一切 → 只 Mock 外部依赖
- ❌ 测试名称模糊 → `test("works")` → `test("should return empty array when no users found")`
- ❌ 断言太弱 → `expect(result).toBeTruthy()` → `expect(result).toEqual([])`
- ❌ 测试和实现耦合 → 测试行为而非实现细节