
故事即是测试
组件所有行为的单一数据来源

想象一下,在没有单元测试的情况下编写一个复杂的函数。你将不得不手动验证每种场景——一遍又一遍。非常麻烦。然而,大多数团队就是这样构建用户界面的。他们调整一些代码,然后尝试手动模拟每个用户界面状态来检查它。使用这种随意的方法很容易遗漏错误。
Storybook 允许你采用更有条理的方法来构建用户界面。你将组件的用例记录为故事(stories),然后在隔离环境中渲染这些故事。这些故事充当单元测试,但用于渲染的组件。你实际上可以在浏览器中验证渲染的输出。
Storybook 是组件的行业标准用户界面开发工作台。Netflix、Slack、Stripe 和全球数千个团队都在使用它,因为它使他们能够为用户界面采用测试驱动的方法。让我们看看如何做到这一点。

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

这就是 Storybook 为用户界面开发人员所做的事情。它对每个组件及其各种使用方式进行编目。组件在隔离环境中渲染,以简化开发和测试。

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

对于不仅仅是简单函数的组件呢?复杂组件依赖于上下文、数据、应用程序状态等等。一旦你在 Storybook 中配置了这些,你就可以在其他测试工具中重用该配置。
可移植的故事
Storybook 及其插件生态系统允许你模拟数据、状态甚至 API 响应。这使你能够在隔离环境中构建和测试复杂的连接组件。更重要的是,你在 Storybook 中进行的任何设置都可以移植到其他测试工具。
@storybook/testing-react 和 @storybook/testing-vue 包提供了实用程序来提升包装你的故事的所有 providers、context 和 decorators。你为隔离组件所做的所有配置都可以在 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 专为用户界面测试而生
在没有单元测试的情况下开发组件不仅耗时更长,而且不可能捕获所有错误。通常很难模拟你想覆盖的所有测试用例。或者你可能会完全遗漏一种状态。Storybook 可以轻松地在隔离环境中渲染组件并探索其所有排列组合。使你能够为构建用户界面采用更有条理和测试驱动的方法。
一个 stories 文件是你组件所有重要用例的目录。它是一个可移植的工件,允许你使用其他测试工具(如 Testing Library、Jest 和 Axe)来验证交互、可访问性和应用程序逻辑。
故事是你组件的单一数据来源。它们允许你在开发期间快速检查外观。使用自动化捕获回归错误或检查功能质量。最后,为你的所有用户界面元素生成必要的文档。
单元测试,但用于用户界面
— Storybook (@storybookjs) 2021年6月30日
每个故事都有一组固定的输入,它在隔离环境中渲染,并允许你直观地检查输出。就像单元测试一样。
你甚至可以添加自动化来
✅ 捕获回归错误
🩺 检查功能质量https://#/qG9bTn8Ve6 pic.twitter.com/6PfT1t0m6I