测试策略指南:单元测试、集成测试、E2E 测试、Mock 策略和测试金字塔
通过 SkillSPAI CLI 一键安装到你的 AI 编码工具:
1. 安装 CLI(如尚未安装)
npm install -g skillspai2. 安装此 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 opencode3. 或指定版本安装
skillspai install testing-strategies@1.0.0 --target claude# 测试策略指南
## 测试金字塔
```
/ 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([])`
- ❌ 测试和实现耦合 → 测试行为而非实现细节