返回博客

复合组件测试

防止小改动演变成重大回归

loading
Varun Vachhar
@winkerVSbecks
最后更新

特斯拉刚刚召回了 158,000 辆汽车,因为一个模块(显示屏)发生故障。如果显示控制台损坏,您将无法访问后视摄像头、转向灯或驾驶员辅助系统。这会显著增加碰撞风险。

一个有缺陷的模块升级为重大故障。

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

上一篇文章中,我们学习了如何使用 Storybook 隔离构建组件,编写可视化测试,并使用 Chromatic 自动捕获回归。但是,测试 UI 中更复杂的部分是很棘手的。它们是通过组合许多更简单的组件创建的,并且也连接到应用程序状态。

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

小错误最终会破坏应用程序

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

Storybook 设计系统中的 Button 组件为例。它在多个页面中被无数次使用。Button 中的错误将无意中导致所有这些页面中的错误。换句话说,一个故障可能会成倍增加。当您在组件层次结构中向上移动到页面级别时,这些错误的影响会增加。因此,我们需要一种方法来尽早发现此类级联问题并找出根本原因。

组合测试

可视化测试通过捕获和比较故事的图像快照(在真实的浏览器中)来捕获错误。这使得它们非常适合发现 UI 更改并识别根本原因。以下是对该过程的快速回顾

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

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

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

让我们看看这个过程是如何运作的。我们将使用我在第 2 部分中介绍的 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 故事文件中定义,我们可以重复使用它们。同样,我们也可以使用默认故事中的 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 组件的 props,那将导致 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

然后,在您的 public 文件夹中生成一个新的 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 邮件列表

获取最新消息、更新和版本

6,730位开发者及更多

我们正在招聘!

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

查看职位

热门文章

如何测试组件交互

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

使用 Storybook 进行可访问性测试

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

Component Story Format 3.0

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

特别感谢 Netlify CircleCI