测试分层方法论
做了一段时间无障碍测试后,我发现很多人都有一个误解——以为写几个端到端用例就万事大吉了。其实不然。测试也需要分层,就像盖楼一样,每层承担不同的责任。这一章我来聊聊分层测试策略,以及单元、集成、E2E这三层的职责该怎么划分。
为什么需要分层测试?
你想想看,如果只测最上层,底下的代码出了问题,定位起来就像大海捞针。我自己经历过一个项目,团队只写E2E测试,跑一轮要40分钟。结果一个低级错误——某个函数返回了undefined,E2E用例在100步之后才报错,定位花费了两天。从那以后,我坚决推行分层测试。
分层的好处很明显:
- 反馈速度:单元测试毫秒级,集成测试秒级,E2E分钟级。你不需要每次都跑全量。
- 定位精准:哪一层失败,问题大概率就在哪一层。
- 维护成本:底层测试更稳定,不会因为UI变动频繁失效。
分层测试策略总览
我把这三层画成了一张图,你看一下就能明白它们之间的关系。
单元层:从最小粒度开始
单元测试,说白了就是测一个函数、一个方法、一个独立的组件。它的核心目标是验证逻辑正确性。无障碍测试里,单元层通常测什么?我举几个例子:
- ARIA属性计算:比如某个函数根据角色和状态返回正确的aria-label
- 焦点管理:键盘事件处理函数是否正确地移动了焦点
- 颜色对比度工具函数:传入两个颜色值,返回是否符合WCAG标准
- 屏幕阅读器文本生成:拼接逻辑是否包含了必要的替代文本
我在项目中曾经遇到过一个问题:一个按钮组件,当disabled属性为true时,应该自动添加aria-disabled="true"。但单元测试只有两个用例——属实和false。后来我补了一个边界用例:传入undefined,结果发现代码直接抛错了。这种问题在集成层和E2E层很难第一时间发现,但单元测试一下就抓住了。
集成层:把碎片拼起来
单元测完,接下来就是集成层。这一层关注的是多个单元之间的协作。无障碍测试里,最常见的是组件之间的交互,比如:
- 弹窗打开时,焦点是否自动移动到弹窗内
- 关闭弹窗后,焦点是否回到触发按钮
- 表单验证失败时,错误信息是否与输入框正确关联
- 导航菜单展开/折叠时,aria-expanded状态是否同步
我个人的习惯是:集成测试用真实DOM渲染(比如JSDOM或HappyDOM),但不要启动真实浏览器。这样可以跑得很快。举个例子,一个下拉菜单组件,你需要测试:点击按钮→菜单出现→按Escape→菜单关闭,并且焦点回到按钮。集成测试里,你直接操作DOM事件,断言DOM变化和焦点状态。
// 集成测试示例(使用Jest + Testing Library)
test('下拉菜单按Escape关闭后焦点回退到触发器', () => {
render(<Dropdown />);
const trigger = screen.getByRole('button', { name: /菜单/ });
trigger.focus();
fireEvent.click(trigger);
expect(screen.getByRole('menu')).toBeVisible();
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Escape' });
expect(document.activeElement).toBe(trigger);
});
你想想看,这种测试在单元层写不了——因为它涉及多个组件;在E2E层写又太慢——还要开浏览器。放在集成层刚刚好。
E2E层:用户视角的最终验证
E2E测试模拟真实用户的操作,用真正的浏览器、屏幕阅读器(或者模拟辅助技术)来验证整个流程。这一层用例最少,但最关键。我的原则是:每条E2E用例必须覆盖一条用户核心路径。
无障碍方面的E2E示例:
- 用户使用Tab键遍历整个表单并成功提交
- 屏幕阅读器朗读商品详情页时,所有关键信息都能被读到
- 使用WCAG的对比度检查插件扫描全页,确保都通过
- 改变窗口大小到320px,查看内容是否仍然可操作
我曾经犯过一个错误:把一个非常复杂的多步表单流程写成了E2E用例,结果每次CI都要花5分钟跑这个用例。后来我把它拆分成:单元层测试每个字段的验证逻辑,集成层测试表单提交的数据组装,E2E只验证完整流程能走通。时间降到了30秒,而且定位问题更快。
各层职责对比
| 维度 | 单元层 | 集成层 | E2E层 |
|---|---|---|---|
| 测试对象 | 函数 / 方法 / 独立组件 | 组件组合 / 服务 / 接口 | 完整用户场景 |
| 执行环境 | Node / 纯JS | JSDOM / HappyDOM / 轻量浏览器 | 真实浏览器(Chrome/Firefox等) |
| 无障碍关注点 | ARIA属性、焦点函数、颜色计算 | 焦点流转、角色状态同步、关联关系 | 键盘导航、屏幕阅读器朗读、对比度验证 |
| 执行速度 | 毫秒级 | 秒级 | 分钟级 |
| 数量占比 | 60%~70% | 20%~30% | 5%~10% |
避坑指南:我踩过的三个坑
- 单元测试mock过度:我曾经把整个DOM API全部mock了,结果测试通过了但页面根本不渲染。现在我会用渲染库(如@testing-library/react)来保持真实DOM行为。
- 集成测试和E2E测试混淆:有些团队把所有涉及UI的都扔到E2E层,结果CI非常慢。后来我明确了一条规则——只要不需要真浏览器键盘事件与屏幕阅读器交互,就放在集成层。
- E2E用例没有优先级:一开始我们把所有功能都用E2E覆盖,跑一次4小时。后来我们只保留最核心的5条无障碍路径,其他的用集成测。效率大幅提升。