← 返回资讯
赵一鸣
产品评测编辑
已审核

单元测试写了 1000 个后,我总结的 5 条铁律

test('calls setState with correct value', () => {

单元测试写了 1000 个后,我总结的 5 条铁律

单元测试写了 1000 个后,我总结的 5 条铁律

铁律 1:测试行为,不测试实现

TYPESCRIPT
// ❌ 测试实现细节
test('calls setState with correct value', () => {
  const spy = jest.spyOn(component, 'setState')
  component.handleClick()
  expect(spy).toHaveBeenCalledWith({ count: 1 })
})

// ✅ 测试用户可见的行为
test('increments counter when button clicked', () => {
  render(<Counter />)
  fireEvent.click(screen.getByText('+'))
  expect(screen.getByText('1')).toBeInTheDocument()
})

铁律 2:一个测试只测一件事

TYPESCRIPT
// ❌ 一个测试测多个行为
test('user login flow', async () => {
  // 测试表单验证
  // 测试 API 调用
  // 测试重定向
  // 测试 token 存储
})

// ✅ 拆分独立测试
test('shows error for empty email', async () => { /* ... */ })
test('shows error for invalid email format', async () => { /* ... */ })
test('calls login API with correct payload', async () => { /* ... */ })
test('redirects to dashboard on success', async () => { /* ... */ })

铁律 3:AAA 模式

TYPESCRIPT
test('adds item to cart', () => {
  // Arrange - 准备数据
  const cart = new Cart()
  const item = { id: 1, name: 'Book', price: 29.9 }
  
  // Act - 执行操作
  cart.add(item)
  
  // Assert - 验证结果
  expect(cart.items).toHaveLength(1)
  expect(cart.total).toBe(29.9)
})

铁律 4:Mock 最少化

TYPESCRIPT
// ❌ 过度 Mock
jest.mock('@/utils/formatDate')
jest.mock('@/utils/validateEmail')
jest.mock('@/hooks/useAuth')
jest.mock('@/services/api')

// ✅ 只 Mock 外部依赖
jest.mock('@/services/api')  // 只 mock API 调用
// 纯函数直接测试,不需要 mock

铁律 5:测试覆盖边界情况

TYPESCRIPT
describe('divide', () => {
  test('divides positive numbers', () => {
    expect(divide(10, 2)).toBe(5)
  })
  test('divides negative numbers', () => {
    expect(divide(-10, 2)).toBe(-5)
  })
  test('throws on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Cannot divide by zero')
  })
  test('handles decimal results', () => {
    expect(divide(1, 3)).toBeCloseTo(0.333, 3)
  })
})

测试结构模板

TYPESCRIPT
describe('ComponentName', () => {
  // 正常路径
  describe('when data is loaded', () => {
    test('renders items', () => { /* ... */ })
  })
  
  // 加载状态
  describe('when loading', () => {
    test('shows spinner', () => { /* ... */ })
  })
  
  // 错误状态
  describe('when error occurs', () => {
    test('shows error message', () => { /* ... */ })
  })
  
  // 空状态
  describe('when no data', () => {
    test('shows empty state', () => { /* ... */ })
  })
})
268
5379 阅读
5 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年07月12日 11:21

赵一鸣

产品评测编辑

前产品经理,现专注 AI 工具评测。实测过 30+ 款 AI 产品,擅长横向对比和用户体验分析。

读者评论 5

运营小陈 6天前
转发到团队群了,大家都觉得有参考价值。
回复 点赞 (4)
数据分析师 1周前
数据引用很扎实,建议补充一下近三个月的最新数据。
回复 点赞 (9)
产品经理阿杰 1周前
从产品角度看,这个方向确实有机会,但商业化路径还需要验证。
回复 点赞 (15)
张工 昨天
写得很实在,特别是实测对比那部分,跟我自己的使用感受一致。
回复 点赞 (12)
前端工程师 4天前
代码示例很清晰,直接用到项目里了。
回复 点赞 (6)