
可视化测试:UI 开发中最伟大的技巧
以更少的维护获得更多信心

在 UI 开发中,确保一切看起来正确与确保其工作一样重要。可视化测试是解决此问题的图像快照测试。
然而,有点令人惊讶的是,它们也可以取代许多 UI 单元测试中最脆弱的部分:断言 UI 的细节。在许多情况下,这可以完全取代单元测试,使您能够用更少的代码测试更多内容。
这篇文章涵盖了
如果您仍在对组件进行单元测试,请继续阅读以了解开发 UI 的更好方法。

可视化测试 101
在我们深入探讨可视化测试为何如此出色之前,它是什么以及它是如何工作的?
可视化测试是一种 快照测试,它比较代码更改之前和之后的 UI 组件图像快照。如果快照不匹配,则测试失败。
- 要么差异是预期的,并且必须更新基线(之前)图像
- 要么差异是意外的,用户应该去修复代码。
以下是在实践中该过程的样子

更少的代码,更好的测试
可视化测试非常简洁,但为什么我们认为它是测试 UI 的一种根本上更好的方法?简短的答案是,可视化测试比单元测试更容易编写和维护。与此同时,它们提供了更多的信心,因为它们测试的内容更多。
考虑一个使用 React Testing Library (RTL) 的简单示例,这是在 Jest 和 Vitest 等测试运行器中单元测试组件的最流行方法。
// Button.test.js
import { render, screen } from '@testing-library/react';
import Button from './Button';
it('uses custom text for the button label', () => {
render(<Button>Click me!</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me!');
})
此测试挂载 Button
组件,然后断言按钮标签的文本内容。Playwright CT 和 Cypress CT 等工具也使用类似的语法和构造。
Storybook 的语法略有不同,但想法相同。这是 RTL 示例的等效示例
// Button.stories.js
import Button from './Button';
export default { component: Button };
export const CustomText = {
args: { children: 'Click me!' },
play: async ({ canvasElement }) => {
await expect(canvasElement).toHaveTextContent('Click me!')
},
};
这是在 Storybook 内部的样子

使用像这样的测试,我们只断言一件事:按钮的文本。
可视化测试不仅断言按钮包含正确的文本,还断言按钮是蓝色的,具有圆角,以相同的字体渲染等等。它们在没有编写单个显式断言的情况下做到这一点。
这是使用 Visual Tests 插件后测试变得多么简单
export const CustomText = {
args: { children: 'Click me!' },
};
在以下示例中,我不小心在全球 CSS 中引入了一个错误,该错误剥夺了 Button 的大部分样式。这将通过 RTL 的功能测试,但我们的可视化测试会捕获差异并将其显示为更改

真实世界示例
节省一行断言可能看起来没什么大不了的,但在真实世界的项目中,好处会迅速累积。考虑像 Mealdrop 的购物车 这样的组件

在功能上,我们希望测试购物车中的所有商品都正确显示,并且结帐按钮已启用,因为购物车中有商品。
通过可视化测试,我们可以使用故事 WithItems
来测试这一点,该故事使用其输入设置购物车,但实际上不包含任何显式测试逻辑
// ShoppingCartMenu.stories.js
import { ShoppingCartMenu } from './ShoppingCartMenu'
export default { component: ShoppingCartMenu };
export const WithItems = {
args: {
cartItems: [ /* items */ ],
totalPrice: 1200
},
}
如果我们不相信启用的按钮会在 UI 中呈现不同的效果,我们可以扩展该测试以定义 WithItemsEnabled
,该测试专门验证按钮未禁用
// ShoppingCartMenu.stories.js
export const WithItemsEnabled = {
...WithItems,
play: async ({ canvasElement }) => {
const checkout = await findByRole(canvasElement, 'button');
await expect(checkout).not.toBeDisabled();
},
}
现在想象一下仅在 RTL 中编写相同的测试。我们想要测试购物车中的每个商品都以正确的数量显示,总数是正确的,等等。
// ShoppingCartMenu.test.js
it('renders correctly with items', () => {
render(<ShoppingCartMenu cartItems={[ /* items */ ]} totalPrice={1200} />);
const fries = await screen.findByText(/^Fries$/);
expect(getByText(fries.parentElement, '€2.50')).toBeInTheDocument();
// More assertions here
const cheeseburger = await screen.findByText(/^Cheeseburger$/);
expect(getByText(cheeseburger.parentElement, '€8.50')).toBeInTheDocument();
// More assertions here
/*
*
*
* Dozens of lines omitted here,
* for everybody's sanity.
*
*
*/
const checkout = screen.getByRole('button');
expect(checkout).not.toBeDisabled();
});
当然,我们可以使用辅助函数来缩短所有这些内容以检查每个购物车商品,但是当我们需要为这样的测试编写和维护辅助函数时,我们就已经失败了。
现在将此单个测试在您的整个应用程序中扩展开来,该应用程序可能包含数百个各种复杂性的组件。维护这些类型的测试是一场噩梦。
相比之下,为数百个组件编写故事并对其进行可视化测试是可行的,并且世界上最好的前端团队已经在这样做了。
测试 UX,而不是实现细节
测试大师 Cory House 最近评论了某人关于“自动化测试就像在代码上浇筑混凝土”的观点。上一节中的 RTL 代码正是人们在自动化测试中抱怨的“混凝土”。
“自动化测试就像在代码上浇筑混凝土。”
— Cory House (@housecor) 2024 年 5 月 26 日
我不同意。
代码是可塑的。混凝土不是。
为了避免测试感觉像“在代码上浇筑混凝土”
1. 在需求明确之前不要编码。
2. 避免要求 100% 代码覆盖率。
3. 测试 UX,而不是…
为了避免混凝土,Cory 建议“测试 UX,而不是实现细节”。而测试 UX 正是可视化测试为我们提供的。更重要的是,可视化快照比代码更容易维护:正如我们在上面看到的,当您的故事以所需状态呈现时,更新测试就像按一个按钮接受新的基线快照一样容易。
由于 Storybook 还支持 RTL 操作和查询,因此您拥有足够的强大功能来根据需要测试所需的详细程度,以获得对代码的信心。
Storybook 中的可视化测试
在 Storybook,我们坚信可视化测试,因此我们已将其作为一流功能包含在内。Storybook 的 Visual Test 插件由 Chromatic 提供支持,Chromatic 是世界上最好的可视化测试基础设施。
Chromatic 通过比较代码更新前后的图像快照来识别更改,并突出显示差异以供审查。它在云端并行运行数千个测试,在数十秒内,跨多个浏览器(Chrome、Safari、Firefox、Edge)、视口、主题和 i18n 区域设置。

Chromatic 提供 PR 检查,以指示 PR 是否存在相关的视觉更改。当测试失败时,用户可以单击进入高效的 UI 以查看视觉更改。到目前为止,PR 检查一直是使用 Chromatic 和其他类似可视化测试服务的主要工作流程。
Storybook 的 Visual Tests 插件 是此工作流程的一种新的创新性转变,它将 Chromatic 的强大功能置于 Storybook 本身内部。这使您可以在开发时按需运行可视化测试,而无需推送代码、运行 CI 和等待一堆不相关的检查。
这是一个很棒的工作流程。从您的组件工作室中,现在可以
- 启动可视化测试
- 过滤侧边栏以突出显示视觉差异
- 在 Storybook 内部查看和解决这些更改

Visual Tests 插件使捕获 UI 错误和在构建组件时保持流畅比以往任何时候都更快。我们相信这是朝着 UI 开发“圣杯”迈出的重要一步。
立即尝试
新的 Storybook 安装中包含 Storybook 的 Visual Test 插件
npx storybook@latest init
如果您是从旧版本的 Storybook 升级,您现在将被提示选择是否要将插件安装到现有项目中
npx storybook@latest upgrade
下一步是什么?
Visual Tests 插件稳定且今天在 Storybook 8 中可用。我们正在考虑以下增强功能
- 全屏查看模式以接受和拒绝更改。
- 将测试范围限定为当前可见的故事或组件的能力。
- 始终在线的“监视模式”,该模式在您的开发机器上本地运行功能测试,并通过更快的反馈循环来补充可视化测试。
有关我们正在考虑和积极开展的项目的概述,请查看 Storybook 的路线图。
我们真的相信可视化测试和我们的新 Visual Tests 插件是测试 UI 的最佳方法,这为您在开发时提供了信心。
— Storybook (@storybookjs) 2024 年 6 月 20 日
我们写了一篇文章来解释原因。https://#/p6IbzHh1B0