
故事是测试
组件一切功能的单一事实来源

想象一下,在没有单元测试的情况下编写一个复杂的函数。您必须手动验证每一种场景——一遍又一遍。这相当麻烦。然而,大多数团队都是这样构建 UI 的。他们修改一些代码,然后尝试手动模拟每一种 UI 状态来检查它。通过这种随意的方法很容易错过 bug。
Storybook 允许您采用更有条理的方式来构建 UI。您将组件的用例记录为故事,然后将它们独立渲染。这些故事就像单元测试,但针对的是已渲染的组件。您实际上可以在浏览器中验证已渲染的输出。
Storybook 是行业标准的组件 UI 开发工作空间。Netflix、Slack、Stripe 以及全球数千个团队都在使用它,因为它允许他们采用面向测试的 UI 构建方法。让我们来看看如何做到这一点。

带眼睛的单元测试
软件一直在变化。您会修复 bug 并发布新功能。然而,在您进行任何更改时都回去验证每个组件是不现实的。尤其是当您没有单元测试来帮助您时。
对比一下大多数团队编写非 UI 代码的方式。每个函数都有一个单元测试套件。它们会在每次推送时执行,以捕获回归。它们还允许开发人员清晰地思考代码在不同场景下需要做什么。
然而,单元测试对于 UI 来说是失败的,因为很难隔离组件,而且大多数测试工具不允许您(开发人员)直观地验证界面。
要测试一个函数,您可以使用 Jest 这样的框架。它使您能够编写明确定义的测试用例并在隔离环境中执行每个用例。值得注意的是,测试在干净的环境中运行,而不是在应用程序中。
如果我们尝试使用此基础结构来测试组件,我们会遇到一个大问题。这些测试没有“眼睛”!它们在 Node 中运行,可能使用 JSDOM。这意味着我们无法查看 UI 并验证其外观。为此,我们必须将每个组件单独渲染——在真正的浏览器中。
我们需要的是一个已编码组件的贴纸手册。
您是否注意到设计师如何设置 Figma 或 Sketch 文件?他们通常会组装一个符号网格。每个符号代表组件的一种状态。任何时候设计师进行更改,他们都可以快速验证所有变体是否正确。

这就是 Storybook 为 UI 开发人员所做的。它目录化了每个组件及其各种使用方式。组件被独立渲染以简化开发和测试。

故事是视觉测试用例
一个测试包含三部分:设置、操作和断言。让我们为 Storybook 分解这个过程。
- 设置:每个故事描述组件的一个用例。您提供该状态所需的适当 props 和数据。
- 操作:Storybook 在浏览器中渲染此组件。
- 断言:您直观地检查故事并手动测试交互。
在开发过程中,您可以循环浏览故事以运行快速的手动验证。您实际上是在为界面编写单元测试。一个视觉单元测试!
也就是说,组件不仅仅是它的外观。您还需要验证它的可访问性、底层逻辑以及它如何融入更大的系统。故事是自动化和其他形式测试的起点。
一次编写,随处测试
在我们开始之前,值得回顾一下组件的各种需要测试的特性:
- 视觉:组件在给定 props 或状态集时是否正确渲染?
- 组合:多个组件是否协同工作?
- 交互:事件是否按预期处理?
- 可访问性:UI 是否可访问?
- 用户流程:跨各种组件的复杂交互是否正常工作?
有不同的工具来检查这些方面。如果您独立测试每个方面,您最终会一遍又一遍地复制组件状态。这在设置和维护方面很麻烦。
*.stories 文件记录了组件的使用方式。它是组件状态的事实来源,也是您想在 UI 中测试的内容。它们是使用基于 ES6 模块的可互操作标准编写的,称为组件故事格式。每个故事都导出为 JavaScript 函数,使您能够与其他工具重用它们。
您可以使用Testing Library 来验证交互和底层逻辑。在Chromatic 中进行视觉回归测试。或者使用Axe 来审计可访问性。或者使用Cypress 来测试用户流程。所有这些都由同一套故事驱动!

更复杂的组件不仅仅是简单的函数。复杂的组件依赖于上下文、数据、应用程序状态等等。一旦您在 Storybook 中配置了这些,您就可以在其他测试工具中重用该配置。
可移植的故事
Storybook 及其插件生态系统允许您模拟数据、状态甚至 API 响应。这使您能够独立构建和测试复杂的连接组件。此外,您在 Storybook 中进行的任何设置都可以移植到其他测试工具。
@storybook/testing-react 和 @storybook/testing-vue 包提供了实用程序来提升包装您的故事的所有提供者、上下文和装饰器。您为隔离组件所做的所有配置都可以在 Jest、Cypress 等中重用。这是一个例子:
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import * as stories from './Button.stories';
/**
* Every component that is returned maps 1:1 with the stories.
* But, they also contain all decorators from story, meta and global levels.
*/
const { Primary, Secondary } = composeStories(stories);
test('renders primary button with default args', () => {
render(<Primary />);
const buttonElement = screen.getByText(
/Text coming from args in stories file!/i
);
expect(buttonElement).not.toBeNull();
});
test('renders primary button with override props', () => {
// You can override props and they will get merged with values from the Story's args
render(<Primary>Hello world</Primary>);
const buttonElement = screen.getByText(/Hello world/i);
expect(buttonElement).not.toBeNull();
});当您将测试用例一次性编写为故事时,很容易在其之上添加任何形式的断言。
Storybook 是为 UI 测试而生的
没有单元测试的组件开发不仅耗时更长,而且无法捕获所有 bug。您很难模拟想要覆盖的所有测试用例。或者您可能完全错过一个状态。Storybook 可以轻松地独立渲染组件并探索它们的所有排列。让您采用更有条理、面向测试的 UI 构建方法。
故事文件是您组件所有重要用例的目录。它是一个可移植的工件,允许您使用 Testing Library、Jest 和 Axe 等其他测试工具来验证交互、可访问性和应用程序逻辑。
故事是您组件的事实来源。它们允许您在开发过程中快速检查外观。使用自动化捕获回归或检查功能质量。最后,为所有 UI 元素生成重要的文档。
单元测试,但用于 UI
— Storybook (@storybookjs) 2021 年 6 月 30 日
每个故事都有固定的输入集,它独立渲染并允许您直观地检查输出。就像单元测试一样。
您甚至可以添加自动化来
✅ 捕获回归
🩺 检查功能质量https://#/qG9bTn8Ve6 pic.twitter.com/6PfT1t0m6I