「要不要写测试」已经没有争议了。真正的问题是——写多少?写在哪一层?
测试金字塔
经典的测试分层理论,前端同样适用:
/\
/E2E\ 少量,覆盖核心流程
/------\
/集成测试\ 中等量,验证模块协作
/----------\
/ 单元测试 \ 大量,验证纯逻辑
/--------------\[!TIP] 建议从集成测试开始,它们的投入产出比最高。纯单元测试和 E2E 测试作为补充。
[!WARNING] 不要为了 100% 覆盖率而测试。覆盖所有分支的测试可能比没有测试更糟糕——它们让重构变得极其痛苦。
测试覆盖率有上限
测试覆盖率可以用数学描述。假设所有代码路径均匀分布,实际能覆盖的场景符合指数增长规律:
其中 是每个测试用例的平均覆盖密度。这意味着前 20% 的测试用例往往覆盖了 80% 的代码路径。
[!NOTE] 一般项目把覆盖率目标定在 70-80% 比较合理。关键模块(支付、鉴权)可以设得更高。
比例建议
| 测试类型 | 占比 | 适用场景 |
|---|---|---|
| 单元测试 | 60% | 工具函数、Hooks、状态管理 |
| 集成测试 | 30% | 表单、列表交互、数据流 |
| E2E | 10% | 注册、下单、支付等核心流程 |
不是越底层越好——测试 UI 实现细节(如某个 div 是否渲染)的单元测试脆弱易碎,性价比极低。
单元测试:聚焦纯逻辑
// utils.ts
export function formatPrice(price: number, currency = 'CNY'): string {
return new Intl.NumberFormat('zh-CN', { style: 'currency', currency }).format(price);
}
// utils.test.ts
import { describe, it, expect } from 'vitest';
describe('formatPrice', () => {
it('formats CNY correctly', () => {
expect(formatPrice(99.9)).toBe('¥99.90');
});
it('handles zero', () => {
expect(formatPrice(0)).toBe('¥0.00');
});
it('formats USD', () => {
expect(formatPrice(10, 'USD')).toBe('US$10.00');
});
});集成测试:验证组件行为
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect } from 'vitest';
describe('SearchInput', () => {
it('calls onSearch after typing and clicking button', async () => {
const user = userEvent.setup();
const onSearch = vi.fn();
render(<SearchInput onSearch={onSearch} />);
await user.type(screen.getByPlaceholderText('搜索...'), 'react');
await user.click(screen.getByRole('button', { name: '搜索' }));
expect(onSearch).toHaveBeenCalledWith('react');
});
});Testing Library 哲学:模拟用户行为,而非测试内部 state。你的测试不会因为重构实现而挂掉。
E2E:保护核心路径
// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';
test('complete checkout flow', async ({ page }) => {
await page.goto('/products/1');
await page.click('button:has-text("加入购物车")');
await page.goto('/cart');
await page.click('button:has-text("结算")');
await page.fill('input[name="address"]', '北京市朝阳区');
await page.click('button:has-text("确认下单")');
await expect(page.locator('.order-success')).toBeVisible();
});只测最重要的 3-5 个流程就够了。
工具推荐
| 场景 | 推荐工具 |
|---|---|
| 单元/集成测试 | Vitest + Testing Library |
| E2E | Playwright |
| 视觉回归 | Chromatic / Percy |
| 覆盖率 | Istanbul (V8 coverage) |
测试不是负担,是让你放心重构的安全网。从 0 到 1 先写几个关键用例,感受到收益后再逐步扩大覆盖率。