
UI 测试指南
一个不会拖慢你的测试工作流程

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

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

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

开发期间
你可以使用 Storybook 只关注 Task 组件,而不是启动整个应用。然后循环浏览其所有 stories,手动验证它们的外观。

PR 检查
对 Task UI 的微调可能导致在使用它的其他组件(TaskList 和 InboxScreen)中出现意想不到的变化。使用 Chromatic 运行可视化测试可以捕获这些变化。它还将确保一切仍然正确连接。
创建拉取请求时,Chromatic 将自动触发。完成后,你将看到一个差异报告进行评审。在这种情况下,这些更改是故意的。点击接受按钮以更新基线。


可访问性测试

开发期间
在开发期间,在 Storybook 内部运行可访问性检查。A11y addon 使用 Axe 审计当前 story,并在插件面板中显示报告。快速浏览可以确认我们的所有 stories 都没有任何违规行为。
PR 检查
要捕获回归 bug,你需要对所有组件运行检查。你可以通过将 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% 的时间修复 bug。测试通过捕获缺陷和加快调试来帮助减少你需要做的工作量。
但是每一个新功能都会引入更多的 UI 和状态,这些都需要测试。保持生产力的唯一方法是实现一个直观的测试工作流程。
从将测试用例编写为 stories 开始。你可以在 Jest、Chromatic 和 Axe 等测试工具中复用它们。研究表明,复用代码可以节省42-81% 的开发时间。
在开发期间,边编码边测试以修复明显的 bug。这缩短了你的反馈循环。它还可以防止 bug 进入生产环境,因为生产环境的 bug 修复成本是10 倍。
最后,使用 CI 服务器对整个 UI 运行所有检查,以防止意外的回归。
我希望将这些经验提炼成一个实用的工作流程能帮助你实现自己的可靠测试策略。让这个工作流程成为你的起点。
选择测试工具很容易,但将它们组合成一个高效的工作流程则不然。
— Storybook (@storybookjs) September 29, 2021
糟糕的选择意味着不稳定的测试和维护噩梦。
本文向你展示了如何实现一个平衡测试投入和价值的实用工作流程。https://#/1wqznNxwiD pic.twitter.com/vP2G4FUKjq