
UI 测试实用指南
不会拖慢您速度的测试工作流程

UI 测试工作流常常会陷入维护的噩梦。只要稍作实现上的调整,您的测试就会中断。您需要为每个工具重复创建测试用例。
不难找到测试 UI 不同部分的工具。但如何将它们组合成一个高效的工作流却颇具挑战。如果处理不当,UI 开发流程就会变得步履维艰。
我采访了来自 Twilio、Adobe、Peloton 和 Shopify 等公司的十个团队,了解他们如何平衡 UI 测试的投入和价值。尽管团队规模和技术栈各不相同,但大家都有相似的策略。本文将这些经验汇集,形成了如下实用的工作流。
- 📚 使用 Storybook 隔离组件。 编写测试用例,通过 props 和 mock 数据重现每种状态。
- ✅ 使用 Chromatic 捕获视觉 bug 并验证组合。
- 🐙 使用 Jest 和 Testing Library 验证交互。
- ♿️ 使用 Axe 审计组件的可访问性。
- 🔄 使用 Cypress 编写端到端测试,验证用户流程。
- 🚥 使用 GitHub Actions 自动运行测试,捕获回归。
行之有效的方法
构建和测试 UI 组件应该是无缝的。这取决于两个方面:减轻维护负担,同时增加运行测试的灵活性。
在组件层面进行测试,更快地发现 bug
我调查的团队也表示,他们大多在组件层面运行测试。组件允许您将界面分解成隔离的单元。隔离测试更容易定位 bug。
重用 Story,减少维护
每种类型的测试都使用不同的工具。这意味着您经常需要一遍又一遍地复制相同的组件状态。这很难设置和维护。Storybook 允许您隔离组件,并将所有测试用例捕获在 *.stories.js 文件中。然后,您可以将它们导入到 Jest 和 Cypress 等工具中。最终,您只需编写一次测试用例。
编码时进行测试,以加快反馈循环
在开发过程中,您专注于与您正在开发的功能相关的少数几个组件。因此,您只想对这些组件运行有针对性的测试。
在合并之前运行所有检查
当您准备合并时,您需要检查回归 bug。这意味着需要使用 CI 服务器自动运行整个测试套件。

UI 测试工作流教程
为了演示这个过程,我将使用Taskbox 应用——一个类似于 Asana 的任务管理应用。之前,我们已经探讨了如何为这个 UI 编写所有不同类型的测试。现在,让我们添加删除任务的功能,并逐步完成整个测试工作流。
为了本次演示,我们直接进入准备测试的阶段。如果您对实现细节感兴趣,可以查看项目仓库。

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

开发过程中
您可以使用 Storybook 来关注 Task 组件,而不是启动整个应用程序。然后,循环浏览其所有 Stories,手动验证其外观。

PR 检查
对 Task UI 的修改可能会导致其在其他组件(TaskList 和 InboxScreen)中的意外更改。使用 Chromatic 运行视觉测试将能发现这些问题。它还将确保所有内容都已正确连接。
创建 pull request 时,Chromatic 会自动触发。完成后,您将看到一个 diff 供您审查。在这种情况下,更改是故意的。按“接受”按钮即可更新基线。


可访问性测试

开发过程中
在开发过程中,在 Storybook 中运行可访问性检查。A11y 插件(A11y addon)使用 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 检查
当 pull request 创建时,GitHub Actions 将运行 Jest 并通过 PR 检查报告状态。

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

PR 检查
与所有其他测试一样,GitHub Actions 也会使用 Cypress 运行 E2E 测试。

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