
UI 测试手册
一个不会拖慢你速度的测试工作流

UI 测试工作流经常陷入维护噩梦。每当实现进行调整时,你的测试就会崩溃。你为每个工具复制测试用例。
很容易找到测试 UI 不同部分的工具。但是,知道如何将它们组合成高效的工作流是很棘手的。如果做错了,UI 开发过程就会感觉像一场苦战。
我采访了来自 Twilio、Adobe、Peloton 和 Shopify 等公司的十个团队,了解他们如何平衡 UI 测试的努力和价值。尽管团队规模和技术栈存在差异,但大家都有类似的策略。本文将这些经验结合起来,形成了下面描述的务实工作流。
- 📚 使用 Storybook 隔离组件。编写测试用例,在其中使用 props 和模拟数据重现每种状态。
- ✅ 使用 Chromatic 捕获视觉错误并验证组合。
- 🐙 使用 Jest 和 Testing Library 验证交互。
- ♿️ 使用 Axe 审核组件的可访问性。
- 🔄 通过使用 Cypress 编写端到端测试来验证用户流程。
- 🚥 通过使用 GitHub Actions 自动运行测试来捕获回归。
有效的方法
构建和测试 UI 组件应该是无缝的。这归结为两个考虑因素:减少维护负担,同时增加运行测试方式的灵活性。
在组件级别进行测试以更快地发现错误
我调查的团队也分享说,他们主要在组件级别运行测试。组件允许您将界面分解为隔离的块。隔离测试使定位错误更容易。
重用 stories 以减少维护
每种类型的测试都使用不同的工具。这意味着您经常一遍又一遍地复制相同的组件状态。设置和维护起来很麻烦。Storybook 使您能够隔离组件并将所有测试用例捕获在 *.stories.js
文件中。然后,您可以将它们导入它们到 Jest 和 Cypress 等工具中。最终结果,您只需编写一次测试用例。
在编码时进行测试以获得更快的反馈循环
在开发过程中,您专注于与您正在开发的功能相关的少数组件。因此,您需要仅在这些组件上运行有针对性的测试。
在合并之前运行所有检查
当您准备合并时,您需要检查回归错误。这意味着使用 CI 服务器自动运行整个测试套件。

UI 测试工作流教程
为了演示这个过程,我将使用 Taskbox 应用程序——一个类似于 Asana 的任务管理应用程序。之前,我们研究了如何为此 UI 编写所有不同类型的测试。现在,让我们添加删除任务的功能,并逐步完成整个测试工作流。
对于此演示,让我们直接跳到您准备好测试的点。如果您对实现感到好奇,请查看项目仓库。

视觉和组合测试
首先,我们只想更新 UI 并确保所有样式都与规范匹配。我们将向 Task 组件添加删除按钮。

在开发过程中
与其启动整个应用程序,不如使用 Storybook 专注于 Task 组件。然后循环浏览其所有 stories 以手动验证其外观。

PR 检查
对 Task UI 的调整可能会导致使用它的其他组件(TaskList 和 InboxScreen)中出现意外更改。使用 Chromatic 运行视觉测试将捕获这些更改。它还将确保一切仍然正确连接。
当您创建拉取请求时,Chromatic 将自动触发。完成后,您将看到一个 diff 以供查看。在本例中,更改是故意的。按下接受按钮以更新基线。


可访问性测试

在开发过程中
在开发过程中,在 Storybook 中运行可访问性检查。A11y 插件使用 Axe 审核活动 story 并在插件面板中显示报告。快速浏览确认我们的 stories 都没有任何违规。
PR 检查
要捕获回归,您需要在所有组件上运行。您可以通过将 stories 导入到 Jest 中,然后使用 jest-axe 运行可访问性审核来做到这一点。所有违规都将报告回 PR 页面。

交互测试
用户可以通过单击垃圾桶按钮删除任务,我们需要添加一个测试来验证该行为。

在开发过程中
在开发过程中,使用 InboxScreen stories 手动验证交互。如果它按预期工作,您可以继续使用 Jest 和 Testing Library 添加交互测试。
// InboxScreen.test.js
import React from 'react';
import '@testing-library/jest-dom/extend-expect';
import {
render,
waitFor,
cleanup,
within,
fireEvent,
} from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import { composeStories } from '@storybook/testing-react';
import { getWorker } from 'msw-storybook-addon';
import * as stories from './InboxScreen.stories';
expect.extend(toHaveNoViolations);
describe('InboxScreen', () => {
afterEach(() => {
cleanup();
});
afterAll(() => getWorker().close());
const { Default } = composeStories(stories);
it('should pin a task', async () => { ... });
it('should archive a task', async () => { ... });
it('should edit a task', async () => { ... });
it('Should have no accessibility violations', async () => { ... });
it('should delete a task', async () => {
const { queryByText, getByRole, getAllByRole } = render(<Default />);
await waitFor(() => {
expect(queryByText('You have no tasks')).not.toBeInTheDocument();
});
const getTask = () => getByRole('listitem', { name: 'Export logo' });
const deleteButton = within(getTask()).getByRole('button', {
name: 'delete',
});
fireEvent.click(deleteButton);
expect(getAllByRole('listitem').length).toBe(5);
});
运行 yarn test
以确认所有测试都通过。请注意 Jest 如何在 watch 模式下运行,并且仅执行与已更改文件相关的测试。

PR 检查
当创建拉取请求时,Github Actions 将运行 Jest,并通过 PR 检查报告状态。

用户流程测试
最后,您需要运行 E2E 测试以确保所有关键用户流程都按预期工作。
在开发过程中
您可以在开发期间运行有针对性的 E2E 测试,但这需要您启动应用程序的完整实例和测试浏览器。这一切都可能非常消耗资源。因此,您可以等到在 CI 服务器上运行 Cypress,除非您正在更新测试。

PR 检查
就像您的所有其他测试一样,Github actions 也将使用 Cypress 运行 E2E 测试。

结论
开发人员花费 21% 的时间 修复错误。测试通过捕获缺陷和加速调试来帮助减少您必须做的工作量。
但是,每个新功能都会引入更多需要测试的 UI 和状态。保持高效的唯一方法是实施直观的测试工作流。
首先将测试用例编写为 stories。您可以在 Jest、Chromatic 和 Axe 等测试工具中重用它们。研究表明,重用代码可以节省 42-81% 的开发时间。
在开发过程中,在编码时进行测试以修复明显的错误。这缩短了您的反馈循环。它还可以防止错误进入生产环境,而修复生产环境中的错误要贵 10 倍。
最后,使用 CI 服务器在整个 UI 上运行所有检查,以防止意外回归。
我希望将这些经验提炼成务实的工作流可以帮助您实施自己的可靠测试策略。让这个工作流成为您的起点。
选择测试工具很容易,但是将它们组合成高效的工作流却并非易事。
— Storybook (@storybookjs) 2021年9月29日
糟糕的选择意味着不稳定的测试和维护噩梦。
本文向您展示如何实施平衡测试工作量和价值的务实工作流。https://#/1wqznNxwiD pic.twitter.com/vP2G4FUKjq