前端测试金字塔如何设计?单元测试、组件测试和 E2E 怎么取舍?
简化版
前端测试通常按金字塔分层:单元测试多而快,组件测试验证 UI 与交互,E2E 少而关键,覆盖核心用户路径。取舍原则是越底层越便宜、越上层越接近真实用户但越慢越脆,不能把所有质量都压给 E2E。
详细版
常见分层:
- 单元测试:工具函数、hooks、状态逻辑。
- 组件测试:组件渲染、事件、边界状态。
- 集成测试:多个模块协作、接口 mock。
- E2E:登录、下单、支付前流程等关键路径。
- 视觉回归:组件库和设计系统常用。
设计原则:
- 核心纯逻辑用单元测试覆盖。
- 复杂组件用组件测试覆盖。
- 关键业务链路用少量 E2E 覆盖。
- 不稳定和低价值用例不要堆太多。
- CI 中分层执行,控制耗时。
完整版教学
一、测试金字塔解决成本分配
测试不是越多越好,而是要用合适成本覆盖合适风险。单元测试快、稳定、定位准,但离用户真实行为远;E2E 接近真实用户,但慢、脆、排查成本高。测试金字塔就是把大部分验证放在底层,把少量关键路径放在顶层。
E2E:少量关键链路
组件/集成:主要 UI 交互
单元测试:大量纯逻辑和边界
如果倒过来,把所有场景都写成 E2E,CI 会很慢,失败也难定位。
二、单元测试适合纯逻辑
格式化金额、权限判断、表单校验、hooks 状态机、数据转换这类逻辑输入输出明确,最适合单元测试。它们运行快,失败后能立刻定位到函数或模块。
数字例子:1000 个单元测试可能 10 秒跑完,100 个 E2E 可能 20 分钟还不稳定。对纯逻辑来说,用 E2E 验证是用大炮打蚊子。
expect(formatPrice(1234)).toBe('¥12.34')
expect(canEdit({ role: 'admin' })).toBe(true)
三、组件测试验证渲染和交互
组件测试关注用户能看到什么、点击后发生什么。比如 Modal 是否展示标题,点击关闭是否触发回调,表单错误是否出现。它比单元测试更接近 UI,又比完整浏览器 E2E 更轻。
| 测试类型 | 适合 | 不适合 |
|---|---|---|
| 单元测试 | 纯函数、状态逻辑 | 浏览器真实行为 |
| 组件测试 | 组件渲染和交互 | 全链路支付 |
| E2E | 核心用户路径 | 大量边界枚举 |
| 视觉回归 | UI 变更检测 | 业务逻辑细节 |
组件测试应尽量从用户视角断言,例如文本、按钮、角色,而不是断言内部 class 名,避免实现细节导致测试脆弱。
四、E2E 只覆盖最值钱的路径
E2E 适合验证核心链路:登录、搜索、下单、提交表单、权限跳转。它能发现前后端集成、路由、真实浏览器兼容和部署配置问题。但它慢且容易受网络、数据和环境影响。
一个项目可以先选 5 到 20 条最关键路径,而不是把每个按钮都写 E2E。比如电商站至少覆盖“登录 → 加购 → 下单确认”,但商品标题截断这种细节不必用 E2E。
五、Mock 策略决定测试稳定性
测试中接口怎么处理很关键。单元和组件测试通常 mock 网络,保证稳定;E2E 可以使用测试环境真实接口,也可以在浏览器层 mock 部分接口。核心是数据可控、环境可重置。
如果 E2E 每次依赖线上真实库存,今天有货明天没货,测试就会随机失败。应准备固定测试账号、测试商品和可重置数据。
六、CI 分层运行更现实
提交前可以跑增量 lint 和相关单元测试;PR 阶段跑全量单元、组件和构建;合并前或定时跑 E2E。分层执行能在反馈速度和质量之间平衡。
测试心法:越底层越多越快,越顶层越少越关键;E2E 是兜关键链路,不是兜所有细节。
七、常见误区与追问
- 误区:E2E 最真实,所以所有测试都写 E2E。 E2E 慢且脆,应只覆盖关键路径。
- 误区:快照测试越多越安全。 大量无意义快照会让 review 失焦,变化时只会机械更新。
- 误区:有测试就一定质量高。 测试要覆盖真实风险,低价值断言只会增加维护成本。
- 追问:组件测试应该断言什么? 从用户视角断言文本、角色、交互结果,少断言内部实现。
- 追问:E2E 数据怎么准备? 使用测试账号、固定数据、环境重置或接口 mock。
- 追问:CI 太慢怎么办? 分层运行、并行化、只在关键阶段跑重测试,并缓存依赖和浏览器。
八、加强记忆
前端测试用“底层多、顶层少、核心链路必须有”来记。纯逻辑单元测,组件交互组件测,真正用户路径 E2E 测;Mock 和测试数据要稳定,CI 分层执行。测试的目标不是堆数量,而是用最低维护成本守住最高业务风险。