返回博客

故事即测试

组件所有功能的单一真相来源

loading
Varun Vachhar
@winkerVSbecks
最后更新

想象一下在没有单元测试的情况下编写一个复杂的函数。你将不得不一次又一次地手动验证每种场景。相当麻烦。然而,大多数团队就是这样构建 UI 的。他们调整一些代码,然后手动模拟每种 UI 状态进行检查。这种随意的方法很容易遗漏 bug。

Storybook 允许你采用更有条理的方法构建 UI。你将组件的用例记录为故事(stories),然后这些故事会被独立地渲染。这些故事就像单元测试,但用于已渲染的组件。你可以在浏览器中实际验证渲染输出。

Storybook 是组件的行业标准 UI 开发工作台。它被 Netflix、Slack、Stripe 以及全球数千个团队使用,因为它允许他们对 UI 采用测试驱动的方法。让我们看看如何实现。

带“眼睛”的单元测试

软件是不断变化的。你修复 bug 并发布新功能。然而,每次更改后都回去验证每个组件是不现实的。尤其是在没有单元测试辅助的情况下。

对比一下大多数团队编写非 UI 代码的方式。每个函数都有一套单元测试。每次推送时都会执行它们以捕获回归。它们还让开发人员能够清晰地思考代码在不同场景下需要做什么。

然而,单元测试对于 UI 来说效果不佳,因为很难隔离组件,而且大多数测试工具不允许你(开发人员)直观地验证界面。

要测试一个函数,你可以使用像 Jest 这样的框架。它让你能够编写定义明确的测试用例,并独立执行每一个。值得注意的是,这些测试在一个干净的环境中运行,而不是在应用中运行。

如果我们尝试使用这个基础设施来测试组件,我们会遇到一个大问题。这些测试没有“眼睛”!它们在 Node 中运行,可能使用 JSDOM。这意味着我们无法查看 UI 并验证其外观。为此,我们必须在真实的浏览器中独立渲染每个组件。

我们需要一个编码组件的“贴纸清单”。
你有没有注意到设计师如何设置 Figma 或 Sketch 文件?他们通常会排列一个符号网格。每个符号代表组件的一种状态。每当设计师进行更改时,他们可以快速验证所有变体的正确性。

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

故事是可视化测试用例

一个测试包含三件事:设置、操作和断言。让我们分解一下 Storybook 的这个过程:

  1. 设置:每个故事描述组件的一个用例。你提供该状态所需的适当的 props 和数据。
  2. 操作:Storybook 在浏览器中渲染此组件。
  3. 断言:你直观地检查故事并手动测试交互。

在开发过程中,你可以循环浏览故事进行快速的手动验证。你实际上是在为界面编写单元测试。一个可视化的单元测试!

话虽如此,组件不仅仅是外观。你还需要验证其可访问性、底层逻辑以及它如何融入更大的系统。故事是自动化和其他形式测试的起点。

一次编写,随处测试

在我们开始之前,有必要回顾一下组件中需要测试的不同特性:

  1. 视觉:给定一组 props 或状态,组件是否正确渲染?
  2. 组合:多个组件是否协同工作?
  3. 交互:事件是否按预期处理?
  4. 可访问性:UI 是否可访问?
  5. 用户流程:跨组件的复杂交互是否正常工作?

有不同的工具可以检查这些方面的每个方面。如果你独立测试每个方面,最终会一遍又一遍地复制组件状态。这设置和维护起来很麻烦。

一个 *.stories 文件是组件如何使用的记录。它是组件状态的真相来源,也是你想要在 UI 中测试的内容。它们使用基于 ES6 模块的可互操作标准编写,称为 Component Story Format。每个故事都导出为一个 JavaScript 函数,使你能够将其与其他工具一起重用。

你可以将它们与 Testing Library 一起使用来验证交互和底层逻辑。在 Chromatic 中进行视觉回归测试。或者使用 Axe 审计可访问性。或者使用 Cypress 测试用户流程。所有这些都由同一组故事驱动!

那些不仅仅是简单函数的组件呢?复杂的组件依赖于上下文、数据、应用状态等等。一旦你在 Storybook 中配置好这些,你就可以在其他测试工具中重用这些配置。

可移植的故事

Storybook 及其插件生态系统允许你模拟数据、状态甚至 API 响应。这使你能够独立构建和测试复杂的连接组件。更重要的是,你在 Storybook 中进行的任何设置都可以移植到其他测试工具。

@storybook/testing-react@storybook/testing-vue 包提供了实用工具,可以将包裹你故事的所有 provider、context 和 decorator 提升出来。你为隔离组件所做的所有配置都可以在 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();
});
Button.test.js

当你将测试用例一次性写成故事时,在其之上叠加任何形式的断言都变得轻而易举。

Storybook 专为 UI 测试而生

在没有单元测试的情况下开发组件不仅耗时更长,而且不可能捕获所有 bug。模拟所有你想覆盖的测试用例通常很困难。或者你可能完全遗漏某种状态。Storybook 使独立渲染组件和探索其所有变体变得容易。这让你能够采用更有条理、测试驱动的方法构建 UI。

故事文件是组件所有重要用例的目录。它是一个可移植的构件,允许你使用 Testing Library、Jest 和 Axe 等其他测试工具来验证交互、可访问性和应用逻辑。

故事是你组件的真相来源。它们允许你在开发过程中快速检查外观。通过自动化捕获回归或检查功能质量。最后,为你所有的 UI 元素生成重要的文档

加入 Storybook 邮件列表

获取最新消息、更新和发布

7,180名开发者及更多

我们正在招聘!

加入 Storybook 和 Chromatic 背后的团队。构建被数十万开发者用于生产环境的工具。远程优先。

查看职位

热门文章

Component Story Format 3.0

告别样板代码,拥抱脚本化交互!
loading
Michael Shilman

测试复合组件

防止小改动演变成大的回归
loading
Varun Vachhar

Storybook 6.3

为 UI 开发优化
loading
Michael Shilman
加入社区
7,180名开发者及更多
缘何为何选择 Storybook组件驱动 UI
文档指南教程更新日志遥测
社区插件参与进来博客
案例展示探索项目组件术语表
开源软件
Storybook - Storybook 中文

特别鸣谢 Netlify CircleCI