返回博客

测试复合组件

防止小改动变成大回归

loading
Varun Vachhar
@winkerVSbecks
最后更新

特斯拉因触摸屏故障召回了 15.8 万辆汽车,因为其中一个模块——显示屏——发生故障。显示屏控制台损坏后,您将无法访问倒车摄像头、转向灯或驾驶员辅助系统。这会大大增加发生碰撞的风险。

一个有缺陷的模块导致了重大故障。

用户界面面临类似的挑战,因为应用程序就像汽车一样,是一个相互关联的零件网络。一个组件中的错误会影响它周围的所有其他组件。更不用说应用程序中每个使用它的部分了。测试 UI 组件的组合方式有助于您预防此类错误。

上一篇文章中,我们学习了如何使用 Storybook 来独立构建组件、编写视觉测试并使用 Chromatic 自动捕获回归。然而,测试 UI 中更复杂的部件仍然很棘手。它们是由许多简单组件组合而成,并且还与应用程序状态连接在一起。

本文将教您如何隔离并对复合组件应用视觉测试。在此过程中,您将学习如何模拟数据和模拟应用程序逻辑。以及测试组件集成的方法。

小的 Bug 会导致应用程序崩溃

应用程序是通过将组件相互连接来构建的。这意味着一个元素中的错误可能会影响其邻居。例如,重命名一个 prop 会扰乱从父组件到子组件的数据流。或者 UI 元素中不正确的 CSS 经常会导致布局损坏。

考虑一下Storybook 设计系统中的 Button 组件。它在多个页面中被无数次使用。Button 组件中的一个错误将不可避免地导致所有这些页面出现错误。换句话说,一次失败会呈指数级增长。当您沿着组件树向上移动到页面级别时,这些错误的数量会增加。因此,我们需要一种方法来及早发现此类级联问题并确定根本原因。

组合测试

视觉测试通过捕获故事的图像快照并在真实浏览器中进行比较来捕获错误。这使其非常适合发现 UI 更改和确定根本原因。以下是该过程的快速回顾:

  1. 🏷 **隔离**组件。使用 Storybook 一次测试一个组件。
  2. ✍🏽 写出**测试用例**。使用 props 重现每个组件状态。
  3. 🔍 手动验证每个测试用例的外观。
  4. 📸 使用视觉回归测试自动**捕获错误**。

组合测试就是对树中更高级别的“复合”组件运行视觉测试,这些组件由几个更简单的组件组成。这样您就可以量化任何更改可能对整个应用程序产生的影响。并确保系统作为一个整体正常工作。

关键区别在于,复合组件会跟踪应用程序状态并将行为向下传递到树中。在编写测试用例时,您必须考虑这些。

让我们来看看这个过程。我们将使用我在第二部分介绍的Taskbox 应用。获取代码并继续。我们的起点是visual-testing分支。

教程

TaskList显示用户拥有的任务的完整列表。它将固定的任务移到列表的顶部。并且具有加载和空状态。我们将从为所有这些场景编写故事开始。

创建一个故事文件,注册TaskList组件,并为默认情况添加一个故事。

// TaskList.stories.js

import React from 'react';
import { TaskList } from './TaskList';
import Task from './Task.stories';

export default {
  component: TaskList,
  title: 'TaskList',
  argTypes: {
    ...Task.argTypes,
  },
};
const Template = (args) => <TaskList {...args} />;

export const Default = Template.bind({});
Default.args = {
  tasks: [
    { id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
    { id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
    {
      id: '3',
      state: 'TASK_INBOX',
      title: 'Write a schema for account avatar component',
    },
    { id: '4', state: 'TASK_INBOX', title: 'Export logo' },
    { id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
    { id: '6', state: 'TASK_INBOX', title: 'Draft monthly blog to customers' },
  ],
};

注意argTypesArgs 是 Storybook 定义故事输入的机制。可以将其视为与框架无关的 props。在组件级别定义的 Args 会自动向下传递到每个故事。在我们的例子中,我们使用Actions 插件定义了三个事件处理程序。 

当您与TaskList交互时,这些模拟的动作将显示在插件面板中。允许您验证组件是否已正确连接。

组合 Args

就像您组合组件来创建新 UI 一样,您可以组合 Args 来创建新故事。一个复合组件的 Args 甚至可以组合其子组件的 Args,这是很常见的。

事件处理程序 Args 已在 Task stories 文件中定义,我们可以重用它们。同样,我们也可以使用默认故事中的 Args 来创建固定的任务故事。

// TaskList.stories.js

import React from 'react';
import { TaskList } from './TaskList';
import Task from './Task.stories';

export default {
  component: TaskList,
  title: 'TaskList',
  argTypes: {
    ...Task.argTypes,
  },
};
const Template = (args) => <TaskList {...args} />;

export const Default = Template.bind({});
Default.args = {
  tasks: [
    { id: '1', state: 'TASK_INBOX', title: 'Build a date picker' },
    { id: '2', state: 'TASK_INBOX', title: 'QA dropdown' },
    {
      id: '3',
      state: 'TASK_INBOX',
      title: 'Write a schema for account avatar component',
    },
    { id: '4', state: 'TASK_INBOX', title: 'Export logo' },
    { id: '5', state: 'TASK_INBOX', title: 'Fix bug in input error state' },
    { id: '6', state: 'TASK_INBOX', title: 'Draft monthly blog to customers' },
  ],
};

export const WithPinnedTasks = Template.bind({});
WithPinnedTasks.args = {
  tasks: [
    { id: '6', title: 'Draft monthly blog to customers', state: 'TASK_PINNED' },
    ...Default.args.tasks.slice(0, 5),
  ],
};

export const Loading = Template.bind({});
Loading.args = {
  tasks: [],
  loading: true,
};

export const Empty = Template.bind({});
Empty.args = {
  ...Loading.args,
  loading: false,
};

通过 Args 组合塑造故事是一项强大的技术。它使我们能够编写故事而无需一遍又一遍地重复相同的 props。更重要的是,它测试了组件集成。如果您重命名了Task组件的一个 prop,那将导致TaskList的测试用例失败。

到目前为止,我们只处理了通过 props 接受数据和回调的组件。当您的组件与 API 连接或具有内部状态时,事情会变得更加棘手。接下来我们将研究如何隔离和测试这类连接的组件。

有状态的复合组件

InboxScreen使用自定义 hook从 Taskbox API 获取数据并管理应用程序状态。就像单元测试一样,我们希望将组件与真实后端分离,并单独测试功能。

InboxScreen

这正是 Storybook 插件的用武之地。它们允许您模拟 API 请求、状态、上下文、提供程序以及您的组件依赖的任何其他内容。The GuardianSidewalk Labs(Google)的团队使用它们来独立构建整个页面。

对于 InboxScreen,我们将使用Mock Service Worker (MSW)在网络级别拦截请求并返回模拟响应。

安装 msw 及其 storybook 插件。

yarn add -D msw msw-storybook-addon

然后,在您的公共文件夹中生成一个新的 service worker。

npx msw init public/

通过将此添加到您的./storybook/preview.js文件中,在 Storybook 中启用 MSW 插件

import { addDecorator } from '@storybook/react';
import { initialize, mswDecorator } from 'msw-storybook-addon';
 
initialize();
addDecorator(mswDecorator);

最后,重新启动yarn storybook命令。我们就可以开始在故事中模拟 API 请求了。

InboxScreen调用useTasks hook,该 hook 又从/tasks端点获取数据。我们可以使用msw参数指定模拟响应。请注意,您可以为每个故事返回不同的响应。

// InboxScreen.stories.js

import React from 'react';
import { rest } from 'msw';
import { InboxScreen } from './InboxScreen';
import { Default as TaskListDefault } from './components/TaskList.stories';

export default {
  component: InboxScreen,
  title: 'InboxScreen',
};

const Template = (args) => <InboxScreen {...args} />;

export const Default = Template.bind({});
Default.parameters = {
  msw: [
    rest.get('/tasks', (req, res, ctx) => {
      return res(ctx.json(TaskListDefault.args));
    }),
  ],
};

export const Error = Template.bind({});
Error.args = {
  error: 'Something',
};
Error.parameters = {
  msw: [
    rest.get('/tasks', (req, res, ctx) => {
      return res(ctx.json([]));
    }),
  ],
};

状态有多种形式。一些应用程序使用 Redux 和 MobX 等库全局跟踪状态片段。或者通过执行 GraphQL 查询。或者它们可能使用容器组件。Storybook 足够灵活,可以支持所有这些场景。有关更多信息,请参阅:用于管理数据和状态的 Storybook 插件

独立构建组件可以减少开发复杂度。您不必启动后端、以用户身份登录并在 UI 中点击才能调试一些 CSS。您可以将其全部设置为一个故事并开始工作。甚至可以为这些故事运行自动回归测试。

捕获回归

在我之前的视觉测试文章中,我们花了一些时间设置 Chromatic 并回顾了基本工作流程。Chromatic 捕获每个故事的快照,并将其与现有基线进行比较。您会看到一个视觉差异,您可以对其进行批准或拒绝。

现在我们为所有复合组件都编写了故事,我们可以通过运行以下命令来执行视觉测试:

npx chromatic --project-token=<project-token>

您应该会看到一个包含 TaskList 和 InboxScreen 故事的差异。

现在尝试更改 Task 组件中的某些内容,例如字体大小或背景颜色。然后提交更改并重新运行 Chromatic。

应用程序的树状结构意味着对 Task 组件的任何调整也将被更高级别组件的测试捕获。测试复合组件可让您在部署到生产环境之前捕获错误。

结论

鉴于现代应用程序的规模,开发人员无法意识到组件被使用的所有不同地方。因此,您经常会意外地发布错误。这会拖慢您的进度——在生产环境中修复这些错误需要 5-10 倍的时间。组合测试使我们能够理解小更改对整个系统的潜在影响。您可以捕获错误,防止其滚雪球般发展成重大回归。

接下来,我们将深入测试交互。当用户完成一个任务时,您如何确保触发了适当的事件并且状态已正确更新?订阅邮件列表,以便在发布更多 UI 测试文章时收到通知。

加入 Storybook 邮件列表

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

7,468开发者及更多

我们正在招聘!

加入 Storybook 和 Chromatic 团队。构建被数十万开发人员在生产中使用的工具。远程优先。

查看职位

热门帖子

如何测试组件交互

了解如何模拟用户行为并运行功能检查
loading
Varun Vachhar

使用 Storybook 进行可访问性测试

通过集成工具快速反馈
loading
Varun Vachhar

组件故事格式 3.0

告别样板,拥抱脚本化交互!
loading
Michael Shilman
加入社区
7,468开发者及更多
原因为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与进来博客
展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI