先说结论:测试从失败风险开始,不从框架开始
资深工程师设计测试时,先问“哪些失败最昂贵、在哪一层能最低成本且稳定地发现”,再选择 Vitest、Jest、Testing Library、Playwright 或其他工具。测试金字塔、测试奖杯和覆盖率都是辅助模型,不能代替项目风险判断。
测试的核心是 test oracle(测试预言):系统出现什么可观察结果才算正确。AI 可以生成大量测试代码,却无法从缺失的需求中自动发明可信的权限规则、报警可靠性、命令状态语义或设备安全行为。如果 oracle 错了,测试执行得再快、覆盖率再高,也只是在稳定验证错误假设。
Vibe Coding 更适合把测试中的机械工作自动化:生成 fixture、组合边界值、搭建 MSW handler、录制 E2E 步骤、整理失败和寻找未覆盖分支。工程师仍然负责失败风险、oracle、环境边界、发布门槛和测试维护成本。
三层掌握标准
| 层级 | 必须掌握或会用 | 验证方式 | AI 的位置 |
|---|---|---|---|
| 必须闭卷掌握 | 可观察行为、test oracle、测试边界、隔离与替身、异步结果、稳定性和失败定位 | 面对一个需求能画出风险表,并说明为何选择单元、组件、集成或 E2E | 不能替代业务和安全语义 |
| 会使用工具验证 | TypeScript/ESLint、Vitest/Jest、Testing Library、MSW/adapter、Playwright/Cypress、CI 报告 | 测试失败能指出具体规则,能保留 trace、日志、截图或协议差异 | 可执行和汇总,结果需人工复核 |
| 可由 AI 初步完成 | 用例与 fixture 初稿、边界数据、E2E 脚本、失败聚类、覆盖缺口和维护性建议 | 审查断言、selector、数据来源、Mock 边界和失败价值 | 加速生成与分类,不决定是否允许发布 |
不依赖 AI 时,至少要能独立写出一个纯逻辑测试、一个组件行为测试,并读懂一条浏览器 E2E 的失败证据。否则很难审查智能体生成的测试是否真正降低风险。
从风险映射到最低成本的测试层级
| 风险或示例 | 最低有效层级 | 候选工具 | 为什么不能只靠 E2E |
|---|---|---|---|
| 坐标转换、格式化、校验、设备状态机 | 单元/属性或参数化测试 | Vitest、Jest | 浏览器链路反馈慢,难穷举边界和非法迁移 |
| 按钮、表单、错误提示和键盘操作 | 组件行为测试 | Vue/React Testing Library | E2E 定位成本高,无法快速覆盖组件状态组合 |
| API 错误、超时和数据协议 | 组件集成或 adapter 测试 | MSW、fake adapter、真实 schema 样本 | 只在完整环境触发故障不稳定,难控制精确响应 |
| 登录、关键任务创建、审批与结果查询 | 少量关键 E2E | Playwright、Cypress | E2E 必要但昂贵;底层分支仍应由更快测试覆盖 |
| 前后端字段和事件版本不兼容 | 契约测试 | schema/OpenAPI/事件契约工具 | UI 成功路径可能碰巧没有触发不兼容字段 |
| CSS、主题、图表或三维视图异常 | 视觉回归 + 关键语义断言 | Playwright/Cypress 截图、视觉平台 | 像素差异有噪声,且截图不能证明交互和数据正确 |
| 键盘、语义和读屏退化 | 组件/E2E 无障碍检查 + 人工检查 | Testing Library、axe、浏览器工具 | 自动规则只能发现部分问题,不能代替真实辅助技术体验 |
| 资源、交互、内存或 Web3D 性能回归 | 可重复性能场景 | Lighthouse、Playwright、Performance API、自定义采样 | 常规功能 E2E 通过不表示性能预算达标 |
“越接近用户越好”与“单元测试越多越好”都过于绝对。合理分布取决于架构、失败成本、依赖数量、反馈速度、环境控制能力和 flaky 风险。同一条关键工作流通常需要少量 E2E 证明连接成立,再用更低层测试覆盖状态和错误组合。
常用工具怎样选择
| 工具或层级 | 适合解决 | 选型边界 |
|---|---|---|
| TypeScript、ESLint | 类型关系、未处理分支、危险模式和一致性 | 不能验证运行时 JSON、浏览器行为和业务结果 |
| Vitest | Vite 项目的单元/组件测试,复用 Vite 解析与配置,兼容许多 Jest 风格 API | 不因为 API 熟悉就假设所有 Jest 插件和行为完全等价 |
| Jest | 已有成熟 Jest 生态、非 Vite 项目或迁移成本高的代码库 | 为追新工具迁移不必然产生业务价值;先看速度、维护和插件依赖 |
| Testing Library | 从角色、名称、文本和用户交互验证 Vue/React 组件的可观察行为 | 不是完整浏览器 E2E;仍需理解框架调度、DOM 环境和异步边界 |
| MSW 或受控 adapter | 在网络/SDK 边界提供真实协议形状、错误、超时和状态组合 | handler 仍可能偏离后端,需要契约样本或集成测试校准 |
| Playwright | 多浏览器上下文、隔离、并行、trace/video/screenshot 和现代 E2E | 测试数量、真实依赖和环境矩阵会提高时间与维护成本 |
| Cypress | 交互式调试、时间旅行体验和既有团队生态 | 选型应看现有基础、目标浏览器、运行模型和团队排障效率 |
| 契约/视觉/无障碍/性能测试 | 处理特定高成本回归 | 只有对应风险和负责人存在时才值得加入,不应为了工具齐全而堆叠 |
Vitest 与 Jest、Playwright 与 Cypress 都不是“新工具必然替代旧工具”的关系。新项目可以按构建栈和团队经验选择;既有项目应先量化当前痛点、迁移成本和收益。Jasmine、Karma 等历史笔记仍可用于维护旧项目,但不再作为这篇总入口的默认工具链。
什么值得单元测试、组件测试和 E2E 测试
单元测试:压缩规则和边界的反馈时间
纯函数、数据转换、校验、排序、权限判断、状态机和重试策略适合单元测试。它们输入输出明确,能够快速覆盖边界值和非法状态。若一个规则只能通过启动浏览器和后端才能测试,通常说明领域逻辑与框架或 I/O 绑得太紧。
单元测试不要求“一个类就是一个单元”。更实用的边界是一项稳定行为。例如设备状态机从 offline 不能直接进入 running,测试应断言迁移结果,而不是私有方法的调用次数。
组件测试:验证用户可观察行为
组件测试关注用户能看到和操作的结果:按钮是否可用、错误是否可理解、表单是否保留输入、键盘能否操作、加载与空状态是否正确。优先使用 role、label、可见文本等语义 selector;确实没有合适语义时再使用稳定的 data-testid。
Vue 和 React 的调度方式不同,但 Testing Library 的原则相同:像用户一样查询和交互,等待可观察结果,不通过 wrapper.vm、组件私有状态或实现函数名定义 oracle。
集成测试:验证拥有的边界
前端通常需要验证 API schema、WebSocket 事件、缓存、路由和状态容器怎样连接。使用 MSW 或受控 adapter 可以准确构造成功、错误、超时、重复和乱序响应,同时避免 Mock fetch、Axios 私有实现或 SDK 内部调用。
Mock server 仍可能与真实后端漂移。关键协议应由 OpenAPI/schema、生产样本脱敏回放、契约测试或受控环境集成校准。
E2E:证明关键链路在真实浏览器中连接成立
E2E 适合登录、关键任务创建、审批、命令提交与状态反馈等少量高价值流程。它覆盖浏览器、路由、网络、存储和渲染的真实组合,但反馈慢、环境依赖多、失败定位成本高,不适合穷举所有状态机分支。
稳定 E2E 需要确定数据、独立账号/空间、可重复清理、语义 selector 和明确等待条件。不要使用任意 sleep(3000) 猜测系统何时完成。
Snapshot 只在意图清晰时使用
Snapshot 适合稳定的序列化结构、协议输出或需要审阅的有限 UI 片段。大范围组件 HTML snapshot 往往把大量无关变化混在一起,审核者容易直接更新文件而不理解行为。关键规则应使用有名字的显式断言。
Mock、异步与 flaky test
Mock 保留在自己拥有的接口
- 为时间、随机数、网络、存储和设备 SDK 定义小接口,在边界替换实现。
- 优先模拟外部行为和协议结果,不逐层 Mock 每个内部函数。
- Mock 返回值必须符合真实 schema,并定期用契约或集成测试校准。
- 如果修改一次内部重构会破坏大量测试,而用户行为没变,测试大概率耦合了实现。
异步测试等待结果,不等待猜测的时间
Promise、框架更新、网络响应和 timer 是不同队列。等待页面出现 role="alert"、按钮解除禁用或任务状态变为完成,比等待固定毫秒更稳定。只有“时间本身”是依赖时才使用 fake timer,例如退避、超时或倒计时;使用后还要恢复真实 timer,避免污染其他用例。
对于无法取消的请求、组件卸载后的回调和快速切换 ID 的竞态,要设计能控制先后顺序的测试。测试不仅断言最终页面,还要证明旧响应不会覆盖新状态。
flaky test 是待诊断缺陷,不是加重试即可
先把不稳定失败分类:
- 测试缺陷:任意 sleep、共享数据、选择器脆弱、清理不完整。
- 产品竞态:真实异步顺序、重复事件、状态所有权或取消逻辑有问题。
- 环境不稳定:浏览器、服务、网络、资源或执行机负载不受控。
- 真实间歇缺陷:只有特定时序或数据组合才出现的产品问题。
自动重试可以收集更多证据,不能把红灯洗成绿灯。临时 quarantine 必须写明负责人、原因、失败证据和解除条件;长期隔离但无人处理,等于删除发布保护。
工业与机器人前端需要额外测试什么
| 风险 | 需要验证的行为 | 建议证据 |
|---|---|---|
| 设备/任务状态迁移 | 非法迁移拒绝,断连后状态不伪装成运行中 | 纯状态机用例、事件日志 |
| 权限与二次确认 | 无权限用户不可下发,危险操作需要正确确认 | 组件/集成/E2E,服务端授权仍须独立验证 |
| WebSocket 乱序、重复和断连 | 旧状态不覆盖新状态,重复消息幂等,重连后能核对状态 | MSW/adapter、录制事件回放 |
| 报警与审计 | 高频遥测采样时报警不被覆盖或静默丢失 | 有界缓冲测试、计数与回放 |
| 外部数据 | payload 作为 unknown 校验版本、类型、范围和设备 ID |
schema 样本、无效数据测试 |
| 命令状态 | 区分已接收、已批准、已下发、执行中、成功、失败和未知 | 契约测试、状态机、浏览器 trace |
| WebGL 画面 | canvas 非空、关键相机/选择交互正确、资源卸载后不持续增长 | screenshot + canvas 像素检查、renderer.info、生命周期测试 |
| 性能 | 长会话队列和内存有上界,关键交互满足项目预算 | 固定数据回放、Performance trace、回归报告 |
视觉回归只能说明像素发生变化,不能证明三维对象语义、坐标或设备状态正确。对 WebGL 应组合截图、canvas 像素非空检查、相机/拾取行为和领域数据断言,并固定 GPU/浏览器环境以控制噪声。
普通浏览器测试默认连接 fake、模拟器或只读环境。未经明确环境门控、权限、确定性安全控制和人工批准流程,E2E 不得触发真实设备运动。浏览器自动化不是机器人安全系统。
Vibe Coding 时代怎样让 AI 参与测试
| AI 可以起草或自动化 | 工程师必须确认 | 输出证据 |
|---|---|---|
| 根据需求草拟风险表和边界值 | 需求是否完整,哪些失败影响发布或安全 | 风险、来源、优先级和负责人 |
| 生成 fixture、参数化数据、MSW handler | 数据隐私、schema 真实性和极端值含义 | fixture 来源、契约版本和失败样本 |
| 生成组件/E2E 脚本和 selector 建议 | test oracle、关键旅程、语义 selector 和环境边界 | 可运行测试、trace、截图和断言说明 |
| 检查缺少断言、重复 setup 和实现耦合 | 修改是否保留业务意图,是否误删必要隔离 | 审查 diff 和前后失败行为 |
| 聚类 CI 失败、重跑和摘要 flaky 模式 | 测试缺陷、产品竞态、环境问题还是间歇缺陷 | 原始日志、trace、出现频率和分类理由 |
给 AI 的输入至少包括业务规则、失败后果、现有边界、禁止副作用、环境和验收条件。只提供组件源码并要求“补齐全部测试”,得到的往往是覆盖率友好、oracle 薄弱的实现细节测试。
智能体适合持续执行固定套件、收集 trace/video、比较失败和提出缺口;工程师需要抽查它是否通过放宽断言、增加重试、修改 fixture 或跳过用例来获得绿色结果。
AI 生成测试审查案例
以下测试可能迅速提高行覆盖率,却没有表达用户或业务风险:
1 | it('works', () => { |
至少存在四个问题:
exists()只证明组件挂载,没有证明任何产品行为。- 大 snapshot 没有指出哪些变化是错误,容易被机械更新。
wrapper.vm.refresh耦合私有实现,重构会失败,真实行为损坏却可能通过。- 没有加载、错误、超时、权限或重试路径,也没有清晰 test oracle。
更有价值的测试从产品行为出发。下面使用 Vue Testing Library 与 MSW 风格 API;imports、server 生命周期和 matcher 配置需要按项目补齐:
1 | it('shows a retry action when loading the device fails', async () => { |
这段示例仍不是完整测试,但 oracle 已经明确:加载失败时,用户看到可理解的报警,并能执行重试。React Testing Library 可以使用同样的 role-based 查询和行为断言;价值来自产品规则,不来自框架语法。
下一步应继续验证点击重试后的请求和状态、重复点击策略、权限、超时与错误日志。AI 可以补初稿,但“失败时是否允许重试、重试是否有副作用”必须来自产品和系统约束。
CI 中怎样分层执行
| 阶段 | 建议内容 | 目标 |
|---|---|---|
| 本地 / pre-commit | formatter、lint、受影响的单元与组件测试 | 数秒到数分钟内发现低成本错误,不阻塞开发流 |
| Pull Request | 完整静态检查、单元/组件/集成、少量浏览器 smoke | 保护主要规则和关键连接,保留可定位报告 |
| 受保护分支 / Nightly | 更广浏览器矩阵、视觉/无障碍/性能、长时间与故障场景 | 承担耗时、噪声更高但有价值的回归 |
| Release / 受控环境 | 契约、迁移、模拟器或硬件门控流程 | 验证发布组合和高风险外部依赖 |
这不是必须照抄的流水线。小型库、普通内容站点和机器人调试平台的风险完全不同。CI 分层需要平衡反馈时间、执行成本、失败定位、环境稀缺性和发布后果。
每个失败门槛都应有负责人和诊断产物。只输出“E2E failed”而没有 trace、网络日志、截图和版本信息,会把自动化节省的时间重新花在猜测上。
面试准备:讲风险和证据,不背 matcher
资深测试题可以统一使用:风险 -> 测试层级 -> oracle -> 隔离方式 -> 失败证据 -> 维护取舍。
| 面试问题 | 回答重点 | 不完整回答 |
|---|---|---|
| 如何测试一个复杂组件 | 列出用户行为和错误状态,说明哪些纯逻辑先抽离,网络怎样控制,保留哪些浏览器验证 | “用 Jest 测方法、用 Cypress 测页面” |
| 单元、集成和 E2E 怎样分配 | 按失败成本、依赖、反馈速度和 flaky 风险选择最低有效层级 | “遵循 70/20/10” |
| Mock 何时有害 | 偏离真实协议、耦合实现、把集成风险隐藏为绿色 | “单测必须隔离全部依赖” |
| 如何治理 flaky test | 分类原因、保存 trace、修复竞态/数据/环境,隔离有负责人和期限 | “失败就重跑三次” |
| 覆盖率有什么价值和限制 | 帮助发现未执行代码,不能证明 oracle、边界和断言质量 | “达到 80% 就能发布” |
| 怎样审查 AI 生成测试 | 检查风险来源、oracle、selector、Mock、失败是否有意义、是否通过放宽断言变绿 | “AI 能自动补全测试” |
记住常用 matcher 和框架生命周期足以读写代码,不必把完整 API 当作面试主线。真正的追问通常是测试为何稳定、为何能发现目标故障、失败后怎样定位,以及维护成本是否匹配风险。
实践任务:关键工作流测试治理
选择设备状态页、视觉任务页或机器人调试流程中的一条关键旅程。
交付物
- 一份包含失败后果、概率、检测层级和优先级的工作流风险表。
- 设备/任务状态机的纯逻辑测试,以及一个 Vue 或 React 组件行为测试。
- 一条 Playwright 或 Cypress 关键路径,保留失败 trace、截图和网络证据。
- 断连、超时、重复/乱序消息用例,普通执行只连接 fake 或模拟器。
- CI 分层方案、一次 flaky test 复盘,以及 AI 初稿与人工审查 diff。
验收标准
- 每个重要失败能指出被破坏的业务规则,而不只是 DOM 或私有方法变化。
- 测试之间数据隔离并能重复运行,没有任意固定 sleep。
- E2E 失败保留 trace 和环境版本,能够区分产品、测试和环境问题。
- 覆盖率只作为辅助信号,不以降低断言或排除关键代码换取数字。
- 涉及真实设备的动作默认关闭,并有独立受控流程。
面试价值
证明能够按风险设计单元、组件、集成和 E2E 边界,治理不稳定测试,并监督 AI 生成物,而不只是熟悉某个测试框架 API。
性能回归的指标、实验和 trace 解释见 前端性能诊断与优化。
暂时不死磕什么
- 不背 Vitest/Jest/Jasmine 的所有 matcher、配置项和 CLI 参数。
- 不追求 100% 覆盖率,也不为提高数字测试没有风险的 getter 和模板细节。
- 不为每个分支创建 E2E;组合爆炸应优先在更低层覆盖。
- 不给所有组件生成大 snapshot,并在失败时无脑更新。
- 不为了工具热度迁移稳定套件,先量化反馈速度、维护成本和缺口。
- 不断言私有方法、框架内部状态和调用次数,除非它们本身就是公开契约。
- 不把普通浏览器自动化扩展成未经门控的真实设备控制。
自检清单
- [ ] 能从失败风险选择最低有效测试层级,而不是先报框架名。
- [ ] 能定义清晰 test oracle,并区分用户行为与实现细节。
- [ ] 能说明 Vitest/Jest、Playwright/Cypress 的条件化选型,而不是宣布唯一赢家。
- [ ] 能用语义 selector、可观察异步结果和受控 adapter 编写稳定测试。
- [ ] 能区分测试缺陷、产品竞态、环境问题和真实间歇缺陷。
- [ ] 能审查 AI 是否生成了空洞断言、失真的 Mock、任意 sleep 或放宽门槛。
- [ ] 能展示工业消息乱序、报警保留、命令状态或 WebGL 中至少一项领域风险测试。
- [ ] 能拿出 CI 失败证据、flaky 复盘和 AI 初稿到修正版的差异。
参考资料
- Vitest documentation
- Jest documentation
- Testing Library: Guiding Principles
- Mock Service Worker documentation
- Playwright documentation
- Cypress documentation
- axe-core
- web.dev: Fast and reliable end-to-end testing with Playwright
官方文档用于查询 API,项目风险表、失败 trace、真实契约和复盘用于决定测什么。两者不能互相替代。