本文属于是语冰的直男翻译了属于是,仅供粉丝参考,英文原味版请临幸 Modern frontend testing with Vitest, Storybook, and Playwright。
向前端工程师提及“测试”,您可能会面临引发 PTSD(创伤后应激障碍)的风险。就传统而言,这事倍功半。毕竟,您可以直接在屏幕上看到 UI;为什么需要编写自动化测试来确认已经在浏览器观察到的内容?
在本文中,我们将分享为什么我们认为前端测试值得一试,为什么它以往风评被害,以及我们采用的使我们的测试易于编写和维护的方案。
我们为什么要测试前端?
测试我们的 UI 有其他不太明显的复利。正如 TDD(测试驱动开发)可以鼓励开发者预先考虑极端用例,UI 组件测试也可以产生相同的效果。
还有,通过使用我们稍后将讨论的工具和技术,我们可以确保满足基本的可访问性规则。
除此之外,拥有一套成熟的前端测试让我们有信心快速迭代和更改产品,而不必担心意外的回归测试。
那为什么不是每个人都编写 UI 测试呢?
如果测试前端代码有这么多好处,为什么经常避免测试呢?不幸的是,时至今日,“成本”方面仍然严重曲解了成本效益等式。
其中一个核心问题是,测试通常在 node.js 环境的 CI 中运行,而不是在代码的实际目标环境 —— 浏览器中运行。
开发者不得不使用 jsdom 等工具模拟(伪造)浏览器 API,但当测试失败时,它们想知道是它们的代码坏掉了,还是只是模拟的行为与真实浏览器不同。
此外,在调试失败的 UI 测试时,测试工具唯一能提供的真正帮助是将一长串 DOM 转储到终端中,这可能很难快速解释。如果这就是你能得到的所有反馈,请您据此弄清楚测试失败的原因,那我只能祝您好运:
图片
为了在实际的浏览器中测试前端代码,某些团队退回到使用端到端测试工具,如 Selenium 或 Cypress,这些工具在真实的浏览器中加载网站。虽然这确实提供了更大程度的置信度,但此类测试可能缓慢而脆弱。
它们更适合验证前端和后端之间的连接,而不是单个组件的行为。
如何编写前端测试?
幸运的是,由于一大坨优秀的开源开发者的努力,在过去几年中,测试前端项目变得更加容易和有价值。
本文的其余部分将讨论我们使用的四种主要类型的测试以及我们用于创建它们的工具。
单元测试(Vitest)
术语“单元测试”可以表示许多不同的含义。于我们而言,这意味着,对存在于组件之外的可重用逻辑代码块进行测试。这些可以是实用程序函数、自定义 React hooks 或最终在组件中导入和使用的任何其他代码。
对于这些,我们使用 Vitest 作为我们的测试运行程序。我们喜欢它自动获取我们的 Vite 配置并毫不费力地处理 ESM 依赖项。它在 6.5 秒内对 40 个文件运行 250 多个测试。我们近一半的测试文件还使用 fast-check 进行基于属性的测试,这意味着,我们可以确信意外输入不会破坏我们的代码。
组件测试(Storybook)
我们构建的大部分内容都是 React 组件。这些组件可以是迷你的可复用组件,如按钮和横幅,也可以是更复杂的组件,如表格和模态框,甚至是整个页面。
甚至整个 App 也是由一大坨其他组件合成的组件。因此,于我们而言,制定一个可靠的策略来测试各种组件至关重要,而我们使用的工具是 Storybook。
Storybook 通常被认为是一种文档工具。许多设计库都会发布一个 Storybook 网站,列出所提供的组件,提供有关它们的有用提示,并演示如何使用它们。作为一个非常小的团队,这不是我们使用 Storybook 的主要原因。
取而代之的是,我们将其用作一个隔离的环境,我们可以在 App 之外构建组件,从而在构建它们所属页面的其余部分之前为我们提供一个处理它们的地方。
我们为每个组件创建不同的“story”,以反映它可能出现的不同状态,如错误条件、空状态、默认状态等——有点像 TDD。
我们甚至可以模拟 API 响应,这样我们就不用等待后端向我们发送数据,然后再开始开发功能。
不久前,Storybook 还添加了播放功能,该函数能够触发与组件的交互,并使用流行的 Testing-Library 工具集断言它们的行为。有了这个,我们可以像用户一样单击并在组件中输入文本,然后确保它的行为符合我们的预期。
我们还发现,由于 Testing-Library 鼓励通过标签或角色而不是 CSS 类来选择元素,因此我们的可访问性也得到了改进。在 CI 中,我们可以使用 Storybook 测试运行程序来执行我们所有的播放功能,并确保没有任何损坏。
我们目前有 1_000
个故事,涉及 200 多个组件,其中一半以上的 story 具有与断言的交互测试,这使得这些测试成为我们绝大多数测试,在 CI 中运行大约需要 3 分钟,分为 6 个作业。
最好的部分是,如果测试失败,我们可以在浏览器中将其拉出并查看出了什么问题,而不是只查看页面的 HTML 标记。
通过创建 story 和测试从按钮到整个 App 的所有复杂程度的组件,我们可以确保它们表现正常。
图片
视觉快照测试(Chromatic)
组件的行为方式确实很重要,但是它的外观呢?残缺的样式不仅会令人尴尬,而且还会导致可用性问题。地球人都知道,用 CSS 编写的样式很容易被意外破坏,对任何类型的基本样式进行更改都可能很可怕。
我刚刚更改了段落的默认边距,但我是否记得检查 App 所有尘土飞扬、无人问津的角落的所有极端用例,比如载入和错误状态?
幸运的是,正如您在上面看到的,我们为每个尘土飞扬的角落和极端情况准备了 Storybook story,所以我们可以在创建每个 story 时拍照,然后在发生任何变化时再次拍照,比较它们以寻找差异。
可以建立一个自主开发的系统来做到这一点,并且有许多服务可以按月付费。我们选择使用 Chromatic,即 Storybook 本身的开发者。
这些视觉快照测试使我们免于多次发布视觉错误,并让我们有信心更改所有标题元素的默认底部间距,而不必担心我们会让 App 中的不明页面看起来残缺不全。
下面是 Chromatic 在重构 PR 中标记的最近更改的示例,该更改阻止了我们发布残缺的样式更改(以绿色显示):
图片
端到端测试(Playwright)
最后,在我们的测试堆栈的顶部是端到端(e2e)测试,用于验证我们部署的 App 是否与部署的数据库和 API 服务器协同工作。
我们的所有其他测试都使用虚假数据将组件置于特定状态,并避免发出可能缓慢且不可靠的网络请求。但是,这意味着,我们需要仔细检查虚假响应是否与真实服务器的实际行为相匹配。
出于这个原因,我们编写了一些测试,用于执行从注册和身份验证流程(使用真正的魔术链接电子邮件和双因素身份验证)到主机创建和删除再到计划升级的所有内容。
这些不需要测试我们的组件在不同情况下的行为(我们的组件测试已经涵盖了这一点),而是测试网站如何与后端交互以实现用户的目标。对于这些测试,我们使用 Playwright,它启动多个浏览器的无头版本。如果测试在 CI 中失败,我们将获得一个调试跟踪,该跟踪显示测试每个步骤的页面状态,我们发现这有助于确定测试失败的原因。
除了每次对前端进行更改时运行这些测试外,我们还会在夜间运行它们,以便在将 API 服务器部署到生产环境之前,在我们的暂存环境中捕获 API 服务器所做的任何意外的重大更改。
图片
荣誉奖(静态分析)
虽然并不总是被认为是“测试”,但还有另一类工具也可以帮助我们避免发布残缺的代码。我们使用静态分析工具,包括 linter(ESLint 和 Stylelint),它们强制执行最佳实践并帮助避免某些类型的错误,我们使用 TS(TypeScript)编写代码,它在 JS 上添加了类型支持,让我们确信我们不会在代码中输入拼写错误或调用实际上不存在的方法。