
视觉测试: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 Addon由Chromatic支持,后者是世界上最好的视觉测试基础设施。
Chromatic通过在代码更新前后比较图像快照来识别更改,并突出显示差异以供审查。它在云端并行运行数千个测试,在几十秒内,跨越多个浏览器(Chrome、Safari、Firefox、Edge)、视口、主题和i18n语言环境。

Chromatic提供PR检查,以指示PR是否存在视觉更改。当测试失败时,用户可以点击进入一个高效的UI来审查视觉更改。直到现在,PR检查一直是使用Chromatic和其他类似视觉测试服务的首选工作流程。
Storybook的Visual Tests Addon是此工作流程的一项创新性新功能,它将Chromatic的功能置于Storybook本身之中。这使您可以在开发过程中按需运行视觉测试,而无需推送代码、运行CI并等待一堆不相关的检查。
这是一个惊人的工作流程。现在,从您的组件工作坊中,您可以
- 发起视觉测试
- 过滤侧边栏以突出显示视觉差异
- 在Storybook中审查和处理这些更改

Visual Tests Addon使捕获UI错误比以往任何时候都更快,并在您构建组件时保持流程。我们相信这是UI开发“圣杯”的重要一步。
立即试用
Storybook的Visual Test Addon已包含在新安装的Storybook中
npx storybook@latest init
如果您从旧版本的Storybook升级,现在将提示您选择是否要将此插件安装到现有项目中
npx storybook@latest upgrade
下一步是什么?
Visual Tests Addon已稳定,并于今日在Storybook 8中可用。我们正在考虑以下增强功能
- 全屏审查模式,用于接受和拒绝更改。
- 能够将测试范围限定在当前可见的故事或组件。
- 始终开启的“监视模式”,可在本地开发机器上运行功能测试,并通过更快的反馈循环来补充视觉测试。
For an overview of projects we’re considering and actively working on, please check out Storybook’s roadmap.
我们非常相信视觉测试和我们新的Visual Tests addon是测试UI的最佳方式,它能在您开发时给您信心。
— Storybook (@storybookjs) 2024年6月20日
我们写了一篇文章来解释原因。https://#/p6IbzHh1B0