返回UI 测试手册
React
章节
  • 简介
  • 可视化
  • 组合
  • 交互
  • 可访问性
  • 用户流程
  • 自动化
  • 工作流程
  • 结论

测试复合组件

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

在 2021 年 1 月,特斯拉召回了 158,000 辆汽车,因为一个模块(显示屏)发生故障。 仪表盘显示屏损坏后,您将无法使用倒车摄像头、转向灯或驾驶员辅助功能。 这大大增加了发生碰撞的风险。

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

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

测试用户界面中更复杂的部分是很棘手的。 它们是通过组合许多更简单的组件创建的,并且也连接到应用程序状态。 在本章中,我们将了解如何隔离复合组件并对其应用可视化测试。 在此过程中,您将学习有关模拟数据和模拟应用程序逻辑的知识,以及测试组件集成的方法。

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

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

minor tweaks cause major regressions

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

The same button component is being used across multiple pages of an app

组合测试

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

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

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

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

让我们通过为 TaskList 组件编写测试来实际了解这个过程,该组件显示属于用户的完整任务列表。

它将固定的任务移动到列表顶部,并且具有加载和空状态。 我们将从为所有这些场景编写故事开始。

Task list has four states: Default, Empty, Loading and Pinned

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

复制
src/components/TaskList.stories.jsx
import TaskList from './TaskList';

import * as TaskStories from './Task.stories';

export default {
  component: TaskList,
  title: 'TaskList',
  argTypes: {
    ...TaskStories.argTypes,
  },
};

export const 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 来创建固定的任务故事。

复制
src/components/TaskList.stories.jsx
import TaskList from './TaskList';

import * as TaskStories from './Task.stories';

export default {
  component: TaskList,
  title: 'TaskList',
  argTypes: {
    ...TaskStories.argTypes,
  },
};

export const 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 = {
  args: {
    tasks: [
      {
        id: '6',
        title: 'Draft monthly blog to customers',
        state: 'TASK_PINNED',
      },
      ...Default.args.tasks.slice(0, 5),
    ],
  },
};
export const Loading = {
  args: {
    tasks: [],
    loading: true,
  },
};
export const Empty = {
  args: {
    ...Loading.args,
    loading: false,
  },
};

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

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

有状态的复合组件

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

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

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

这已经在介绍章节中传输的模板中提供。 我们需要设置它。 让我们看看如何操作。

运行以下命令以在您的 public 文件夹中生成一个新的 service worker。

复制
yarn init-msw

💡 Public 目录可能因项目而异。 对于自定义配置,我们建议阅读 MSW 的文档以了解更多信息。 要查看 Storybook 中反映的更改,您需要更新 staticDirs 配置元素在 .storybook/main.js 中。

在您的 .storybook/preview.js 文件中启用 MSW 插件

复制
.storybook/preview.js
import '../src/index.css';

+ import { initialize, mswLoader } from 'msw-storybook-addon';

+ // Initialize MSW
+ initialize();

/** @type { import('@storybook/react').Preview } */
const preview = {
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
+ loaders: [mswLoader],
};

export default preview;

最后,重启 yarn storybook 命令。 我们都已准备好在故事中模拟 API 请求。

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

复制
src/InboxScreen.stories.jsx
import { http, HttpResponse } from 'msw';

import InboxScreen from './InboxScreen';

import { Default as TaskListDefault } from './components/TaskList.stories';

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

export const Default = {
  parameters: {
    msw: {
      handlers: [
        http.get('/tasks', () => {
          return HttpResponse.json(TaskListDefault.args);
        }),
      ],
    },
  },
};

export const Error = {
  args: {
    error: 'Something',
  },
  parameters: {
    msw: {
      handlers: [
        http.get('/tasks', () => {
          return HttpResponse.json([]);
        }),
      ],
    },
  },
};

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

隔离构建组件可以降低开发的复杂性。 您不必启动后端、以用户身份登录并在 UI 中点击来调试一些 CSS。 您可以将所有内容设置为一个故事并开始进行。 您甚至可以在这些故事上运行自动化回归测试。

捕获回归

上一章中,我们设置了 Chromatic 并回顾了基本工作流程。 现在我们已经为所有复合组件编写了故事,我们可以通过运行以下命令来执行可视化测试

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

您应该看到一个差异,其中包括 TaskList 和 InboxScreen 的故事。

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

应用程序的树状结构意味着对 Task 组件的任何调整也将被更高级别组件的测试捕获。 组合测试使您能够了解每个小更改的潜在影响。

验证组件功能

接下来,我们将超越外观,进入交互测试。 当用户选中一个任务时,您如何确保触发了合适的事件并且状态已正确更新?

使您的代码与本章保持同步。 在 GitHub 上查看 9c49e13。
这个免费指南对您有帮助吗? 发推文表示赞赏并帮助其他开发者找到它。
下一章
交互
了解如何模拟用户行为并运行功能检查
✍️ 在 GitHub 上编辑 – 欢迎 PR!
加入社区
6,721位开发者正在加入
为什么为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

由以下机构维护
Chromatic - Storybook 中文
特别感谢 Netlify CircleCI