测试基础与框架
测试分类
成本高 · 运行慢 · 信心足
┌─────────┐
│ E2E │
│ 测试 │
└────┬────┘
┌────┴────┐
│ 集成 │
│ 测试 │
└────┬────┘
┌────┴────┐
│ 单元 │
│ 测试 │
└─────────┘
成本低 · 运行快 · 粒度细 成本高 · 运行慢 · 信心足
┌─────────┐
│ E2E │
│ 测试 │
└────┬────┘
┌────┴────┐
│ 集成 │
│ 测试 │
└────┬────┘
┌────┴────┐
│ 单元 │
│ 测试 │
└─────────┘
成本低 · 运行快 · 粒度细| 类型 | 范围 | 速度 | 维护成本 | 覆盖率 |
|---|---|---|---|---|
| 单元测试 | 函数/组件 | ⚡ 毫秒 | 低 | 逻辑覆盖 |
| 集成测试 | 模块交互 | ⏱ 秒级 | 中 | 功能覆盖 |
| E2E 测试 | 完整流程 | 🐢 分钟 | 高 | 场景覆盖 |
测试框架选型
| 框架 | 特点 | 适用场景 |
|---|---|---|
| Vitest | Vite 原生,兼容 Jest API,极速 | 现代项目首选 |
| Jest | 生态最成熟 | Webpack 项目 |
| Mocha + Chai | 灵活可配置 | 自定义需求 |
| Playwright | E2E 测试,跨浏览器 | 浏览器自动化 |
| Cypress | E2E 测试,调试体验好 | E2E / 组件测试 |
| Testing Library | 行为驱动测试 | 组件测试 |
AAA 模式
js
// Arrange(准备) → Act(执行) → Assert(断言)
describe('calculateTotal', () => {
it('should return sum of items', () => {
// Arrange
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 },
]
// Act
const result = calculateTotal(items)
// Assert
expect(result).toBe(35)
})
})// Arrange(准备) → Act(执行) → Assert(断言)
describe('calculateTotal', () => {
it('should return sum of items', () => {
// Arrange
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 },
]
// Act
const result = calculateTotal(items)
// Assert
expect(result).toBe(35)
})
})Mock 与 Stub
js
// Mock 函数
const fn = vi.fn()
fn.mockReturnValue(42)
fn.mockImplementation(x => x * 2)
// 验证调用
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledWith(1)
expect(fn).toHaveBeenCalledTimes(3)
// Mock 模块
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}))
// Mock 时间
vi.useFakeTimers()
vi.advanceTimersByTime(1000)
vi.useRealTimers()// Mock 函数
const fn = vi.fn()
fn.mockReturnValue(42)
fn.mockImplementation(x => x * 2)
// 验证调用
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledWith(1)
expect(fn).toHaveBeenCalledTimes(3)
// Mock 模块
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' }),
}))
// Mock 时间
vi.useFakeTimers()
vi.advanceTimersByTime(1000)
vi.useRealTimers()断言示例
js
// 基础断言
expect(value).toBe(true) // 严格相等(Object.is)
expect(value).toEqual({ a: 1 }) // 深度相等
expect(value).toBeNull()
expect(value).toBeDefined()
expect(value).toBeUndefined()
// 数字
expect(value).toBeGreaterThan(10)
expect(value).toBeLessThanOrEqual(100)
// 字符串
expect(str).toMatch(/hello/)
expect(str).toContain('world')
// 数组/Set
expect(arr).toContain(1)
expect(arr).toHaveLength(3)
// 异常
expect(() => parse('')).toThrow()
expect(() => parse('')).toThrow('Invalid input')
// 快照
expect(component).toMatchSnapshot()// 基础断言
expect(value).toBe(true) // 严格相等(Object.is)
expect(value).toEqual({ a: 1 }) // 深度相等
expect(value).toBeNull()
expect(value).toBeDefined()
expect(value).toBeUndefined()
// 数字
expect(value).toBeGreaterThan(10)
expect(value).toBeLessThanOrEqual(100)
// 字符串
expect(str).toMatch(/hello/)
expect(str).toContain('world')
// 数组/Set
expect(arr).toContain(1)
expect(arr).toHaveLength(3)
// 异常
expect(() => parse('')).toThrow()
expect(() => parse('')).toThrow('Invalid input')
// 快照
expect(component).toMatchSnapshot()
Cao Note