
如何切实地测试 UI
领先工程团队使用的测试技术

测试 UI 颇为棘手。用户期望频繁发布包含新功能的版本。但每个新功能都会引入更多 UI 和新状态,你需要对此进行测试。每个测试工具都承诺“简单、稳定、快速”,但在细节说明中也存在权衡。
领先的前端团队是如何跟上节奏的?他们的测试策略是什么,使用了哪些方法?我调研了 Storybook 社区的十个团队,了解了哪些方法行之有效——包括 Twilio、Adobe、Peloton、Shopify 等等。
本文重点介绍规模化工程团队使用的 UI 测试技术。这样,你可以制定一个务实的测试策略,在覆盖范围、设置和维护之间取得平衡。在此过程中,我们将指出需要避免的陷阱。
我们正在测试什么?
所有主要的 JavaScript 框架都是组件驱动的。这意味着 UI 是从“自下而上”构建的,从原子组件开始,逐步组合成页面。
记住,UI 的每一部分现在都是一个组件。是的,包括页面。页面和按钮之间唯一的区别在于它们如何消费数据。
因此,测试 UI 现在等同于测试组件。

对于组件而言,不同测试方法之间的界限可能模糊不清。与其关注术语,不如思考 UI 的哪些特性值得测试。
- 视觉:给定一组 props 或状态,组件是否正确渲染?
- 组合:多个组件是否协同工作?
- 交互:事件是否按预期处理?
- 可访问性:UI 是否可访问?
- 用户流程:跨多个组件的复杂交互是否起作用?
你应该重点关注哪里?
全面的 UI 测试策略需要在投入和价值之间取得平衡。但测试方法实在太多,很难确定哪种方法适用于特定情况。这就是为什么许多团队使用以下标准来评估不同的测试技术。
- 💰 维护成本:编写和维护测试所需的时间和精力。
- ⏱️ 迭代速度:进行更改和查看结果之间的反馈循环有多快。
- 🖼 真实环境:测试执行的环境——在真实浏览器中还是在 JSDOM 等模拟环境中。
- 🔍 隔离故障:测试失败时,你能多快确定故障源。
- 🤒 测试不稳定(Flake):误报/漏报会使测试失去意义。
例如,端到端测试模拟“真实”用户流程,但在所有地方应用并不实际。在 Web 浏览器中测试的关键优势同时也是劣势。测试运行时间更长,且失败点更多(不稳定!)。
既然我们已经讨论了要测试的 UI 特性和评估每种测试方法的标准,接下来看看团队如何设计他们的测试策略。
"测试让我对自动化依赖更新充满信心。如果测试通过,我们就将其合并。"
— Simon Taggart,Twilio 首席工程师
视觉测试:看起来对吗?
现代界面有无数种变化。变化越多,就越难确认它们在用户设备和浏览器中都正确渲染。
过去,你必须启动应用程序,导航到一个页面,然后进行各种操作才能将 UI 置于正确状态。

组件结构允许你根据 props 和 state 渲染特定变体。你无需启动整个应用程序来查看组件如何渲染,只需传入 props 和 state 即可在隔离环境中查看它。
Twilio 和 Shopify 使用 Storybook 隔离组件、模拟其变体,并将支持的测试用例记录为“stories”。这使得开发人员可以在初始开发期间以及质量保证阶段对组件外观进行抽查。

然而,考虑到应用程序的规模,手动测试 UI 外观并不实际。每当你调整 UI 时,都必须检查每个组件在所有断点和浏览器下的变体。工作量巨大!
Auth0 和 Radix UI 自动化了 UI 验证过程。他们使用视觉测试在一致的浏览器环境中捕获每个 UI 组件的屏幕截图,包括标记、样式和其他资源。这样,他们就能测试用户实际看到的内容。
每次提交时,新的屏幕截图都会自动与之前接受的基线屏幕截图进行比较。当机器检测到视觉差异时,开发人员会收到通知,以批准有意更改或修复意外错误。

但是 DOM 快照测试呢?评估一堆 HTML 的缺点已经有据可查了。
值得吗?
始终值得。视觉测试投入少,价值高。它们维护工作量极少,在真实浏览器中执行,且不稳定率低。
组合测试:这是否协同工作?
当组件组合在一起时,往往会发生奇怪的事情。UI 由许多简单的组件组成。验证这些组件如何集成,可确保整个系统正常工作。
但测试组合很棘手,因为复杂功能通常与数据和应用程序状态绑定。这需要你模拟或仿真应用程序的业务逻辑。

BBC 和 Sidewalk Labs (Google) 使用 Storybook 在隔离环境中构建复合组件。Storybook 的插件简化了数据、事件和 API 响应的模拟。一旦你的 UI 在 Storybook 中隔离,你就可以进行视觉测试,验证组件集成,一直到页面级别。



值得吗?
通常值得。这些测试需要一些投入,但它们能发现不易察觉的集成问题,这些问题否则很难追踪。
交互测试:按下这个按钮会发生什么?
界面不是静态的。用户可以与 UI 交互,填写表单字段并触发事件。
你如何确保 UI 正确响应交互?我们可以使用计算机模拟和验证用户交互!

一种方法是使用像 Enzyme 这样的工具来访问组件的内部方法。然后触发状态更改并检查结果。这种方法可行,但最终你测试的是内部工作原理,而不是像用户那样与 UI 交互。
这就是为什么现在大多数团队使用Testing-Library,因为它评估的是组件的输出。它的工作原理是在虚拟浏览器 (JSDOM) 中渲染整个组件树,并提供模拟真实世界用法的工具。

Adobe 旨在更进一步,将组件用例编写为 stories。然后在 Jest 中重用它们来运行交互测试。这得益于组件 Story 格式——一种基于 JavaScript ES6 模块的可移植格式。因此,你可以在开发过程中使用相同的 story,然后在视觉、组合和交互测试中再次使用。
值得吗?
有时值得。交互测试确保组件之间的连接正常工作。事件正在流动,并且状态正在更新。在实践中,这意味着通过编写相对低维护成本的测试,你可以获得中等程度的覆盖率。
可访问性测试 – 应用程序对所有用户都可用吗?
你的用户以多种方式与 UI 交互。例如,使用鼠标、触摸屏、键盘和屏幕阅读器。可访问性是让网站对所有人可用的实践。

测试可访问性的最准确方法是在浏览器、设备和屏幕阅读器的组合中手动检查。公司通常会聘请外部顾问或在内部培训人员。但这可能不切实际,因为手动测试每次 UI 更改非常耗时。这就是为什么团队采用结合手动测试和自动化的混合方法。
作为第一道质量保证防线,使用机器捕获明显的可访问性违规。这通过对照一组最佳实践启发式规则(例如,使用像 Axe 这样的库)审计渲染的 DOM 来实现。自动化检查完成后,手动抽查 UI 以发现细微问题。
结合自动化和手动测试,最终在覆盖范围和投入之间取得务实平衡。你可以获得快速反馈循环,在可访问性问题上线前发现并修复它们。大多数团队使用 Axe 对组件运行自动化检查。这还能让他们进行有针对性的测试,更快地发现 bug。例如:
- 原子组件:评估键盘可操作性、不良颜色对比或缺失 aria 属性。
- 组合:验证组合组件不会相互阻碍行为。
- 页面:确保所有标题和各个部分以正确的顺序出现。

前 Twilio Paste 团队使用 jest-axe 集成对组件运行自动化可访问性审计。Axe 也可作为Storybook 的插件使用。
值得吗?
始终值得。它不仅对你的用户大有裨益,也是一项法律要求。Axe 是一个低投入的工具。使用它并不能自动让你的应用程序变得可访问,但它可以在早期发现许多问题。
用户流程测试 – 你的应用程序是否端到端正常工作?
即使是最基本的任务,也需要用户跨多个组件完成一系列步骤。这又是另一个潜在的故障点。像 Cypress 和 Playwright 这样的工具允许你针对整个应用程序运行端到端 (E2E) 测试来验证此类交互。

测试整个应用程序需要大量的基础设施工作。你必须创建一个测试环境,以便同时部署系统的所有部分——前端、后端和其他服务。准备测试数据。然后连接到云浏览器来实际运行测试。
考虑到这些权衡,大多数团队选择放弃全面的 UI E2E 测试,转而倾向于交互和组合测试。或者他们将 E2E 测试限制在少量用例上,以确保应用程序部署到生产环境后仍然正常工作。
然而,对于一些团队来说,这种权衡是值得的。例如,O'Reilly 使用 Docker 启动他们的整个基础设施。然后使用 Cypress 运行 E2E 测试来验证用户旅程。

值得吗?
谨慎使用。E2E 测试需要显著的权衡。它们提供了高度的信心,但需要时间和精力来启动和测试整个系统。因此,将 E2E 测试限制在关键用户流程上,例如:注册 → 添加到购物车 → 购买。
自动化那些无聊的部分
如果你和我一样是一名开发人员,构建 UI 比测试每种状态更有趣。那么你如何测试所有功能同时仍有时间编写代码呢?
我采访的每个团队都使用持续集成(CI)服务器来减少手动工作。每次你推送代码时,CI 都会自动触发你的测试套件。测试在后台执行,结果报告到 pull request 供所有人审查。
自动化的 CI 检查会自动检测 UI bug,让你在部署到生产环境之前对 UI 的“外观和感觉”充满信心。

你的 UI 测试策略
UI 测试是交付高质量体验不可或缺的一部分。由于应用程序的表面范围很广,并且有许多测试方法,因此很难确定一个务实的测试策略。
最终需要在权衡中取得平衡。有些测试易于维护但提供虚假保证。另一些测试评估整个系统但速度较慢。
在采访了十个团队,以确定哪些 UI 测试方法真正有效后,我汇编了一份他们推荐的工具清单。
- 📚 Storybook,用于隔离组件,简化测试。
- ✅ Chromatic,用于捕获原子组件中的视觉 bug,并验证组件组合/集成。
- 🐙 Testing Library,用于验证交互和底层逻辑。
- ♿️ Axe,用于审计可访问性
- 🔄 Cypress,用于验证跨多个组件的用户流程
- 🚥 GitHub Actions,用于持续集成
下表总结了每种 UI 测试方法的优缺点及其使用频率。
到目前为止,本文仅触及了 UI 测试的皮毛。在接下来的文章中,我将深入探讨测试堆栈的每一层,并介绍如何实现 UI 测试策略的机制。加入邮件列表,以便在更多测试文章发布时收到通知。
