交互测试的基础知识介绍及用React 18和Storybook做交互测试的教程

841 阅读9分钟

在这篇文章中,我们将专注于在使用React 18和Storybook时测试我们组件的交互。在我们开始之前,你可能想阅读这篇来自于Chak Shun Yu的介绍性文章,它探讨了React 18的新API,以及宣布React 18的博文,它提供了对React 18新的、选择的并发渲染能力的深入了解。

什么是交互测试?

交互测试包括测试React组件内的交互。这些测试模拟了用户在应用程序中的行为,例如通过点击按钮或向输入组件输入信息与组件进行交互,以及在这种交互之后的应用程序的行为。

在现代应用中,组件所承担的责任比以前多得多,因为开发者已经开始在组件中加入数据获取操作和状态管理工具等内容。通过交互测试,我们可以更有效地验证我们应用程序的用户界面的功能方面。

Storybook是如何帮助交互测试的?

Storybook是一个开源工具,可以让你孤立地构建UI组件和页面。它允许你为组件建立文档,这使得组件易于重复使用,并允许对它们进行可视化测试,这有助于防止bug。

要开始使用Storybook,我们需要在一个现有项目的根目录下运行Storybook CLI。在这种情况下,我们的项目是一个React应用程序,使用Create React App创建。

运行下面的命令,启动React应用程序并添加一个故事。

创建React应用程序:

npx create-react-app reactstorybook -template typescript
yarn start

打开另一个终端窗口,在我们项目的根目录下初始化Storybook:

npx storybook init
yarn storybook

一旦初始化完成,Storybook应该在本地启动并输出一个地址。根据你电脑的配置,Storybook环境可能会在浏览器中自动打开。

Welcome to Storybook introduction

故事是一个接受一组参数的组件,这些参数描述了如何渲染该组件。它本质上捕捉了一个UI组件的渲染状态。

在我们的Create React App中初始化Storybook后,Storybook创建了一些示例组件,展示了可以用Storybook构建的组件的类型,即按钮、标题和页面。

这些示例组件概述了Storybook如何为不同的故事管理状态。在Storybook UI上显示的按钮组件的故事可以在我们的代码编辑器中找到。

在React应用程序的根目录下,打开终端窗口,输入以下命令来查看Button组件的代码和它的故事。

code src/stories/Button.stories.tsx 
code src/stories/Button.tsx

来自Storybook的故事的一个关键重要性是使开发人员能够跟踪UI的外观,因为做出了改变。这有助于防止意外的回归,Timothy Vernon在他的文章中谈到了这一点,他为开发者提供了使用Jest处理视觉回归的测试策略。

在Storybook中测试组件的方法

Storybook提供了一个隔离测试组件的环境。测试已经写好的故事是很重要的,因为它有助于防止随着时间的推移出现UI错误。

Storybook带有各种工具,能够测试UI、模拟数据或依赖关系,甚至模拟网络请求。测试运行器就是这样的工具之一。它使开发者能够自动测试整个Storybook,并捕捉破损的故事。

在使用Storybook进行测试时,有各种测试方法可供开发者使用。

视觉回归测试

视觉回归测试有助于捕捉UI外观的错误。它的工作原理是对每个故事进行截图,并对其进行比较以识别变化。Storybook非常适用于运行视觉测试,因为每个故事都是一个测试规范。

视觉回归测试不应该与快照测试相混淆,因为快照的工作方式是将每个故事的渲染标记与已知的基线进行比较。快照测试不像视觉回归测试那样准确,因为它们比较的是HTML块,而不是显示给用户的东西。这可能会导致假阳性,因为代码变化不会产生视觉变化。

可访问性测试

质量保证专业人员依靠可及性测试来捕捉应用程序开发过程中出现的可及性变化。可访问性测试依靠《网络内容可访问性指南》(WCAG)的规则来审核渲染的DOM。如果呈现的DOM中的元素不符合WCAG规则,就会出现无障碍性违规。

Storybook提供了一个非常方便的工具,通过a11y插件 测试应用程序中的可访问性

如何在React 18中用Storybook进行交互测试

现在,是时候看看在Storybook和React 18中,交互测试是如何进行的。

如前所述,交互测试测试与React组件的交互。在交互测试中,目标是假装成用户,这涉及到输入和点击按钮等动作,并确保收到预期的输出。

React组件的主要目标是给定一组道具来渲染用户界面,但也有一些复杂的组件跟踪应用程序的状态。

Storybook 6.4版本中包含的play 函数,一旦故事被加载就会执行。play 函数是测试我们组件的关键;通过play 函数,你可以启用模拟用户行为的能力。然后,测试运行器可以用来确认组件的渲染是否正确,并检查与play 函数的交互测试是否通过。

有两个关键的软件包,你需要安装它才能工作:

在我们项目的根目录下打开终端,安装列出的软件包:

yarn add -dev @storybook/testing-library @storybook/jest @storybook/test-runner jest@27

@storybook/addon-interactions 如果你使用的是旧版本的Storybook,请检查你的package.json ,确认在devDependencies 。如果不是,请用--dev 标志安装该包,因为它是在Storybook中设置交互测试不可或缺的。

如何用交互附加组件更新你的Storybook配置

module.exports = {
  stories: ["../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions", // Addon has to be registered
    "@storybook/preset-create-react-app",
  ],
  features: {
    interactionsDebugger: true, // enable playback controls
  },
  framework: "@storybook/react",
  core: {
    builder: "@storybook/builder-webpack5",
  },
};

用Storybook和play 函数创建一个交互测试

play 函数持有测试的逻辑,并与我们要测试的故事相连。让我们看看如何用Storybook和play 函数来设置一个交互测试。

我们依靠@storybook/testing-library@storybook/jest 包来帮助完成这个工作。

创建一个Storybook组件

为了体验幕后发生的事情,我们将创建一个有两种状态的表单组件:

  1. 一种状态是表单没有输入
  2. 一个是由play 函数处理的状态,使用交互来填写输入的内容

stories 目录中,创建文件Form.tsxForm.stories.tsx

touch src/stories/Form.tsx
touch src/stories/form.css
touch src/stories/Form.stories.tsx

Form.tsx 是我们的第一个组件。在该文件中,粘贴以下代码:

import React from "react";
import { Button } from "./Button";
import "./form.css";

interface FormProps {
  placeholder?: string;
  label?: string;
  name?: string;
}

export const Form = ({
  placeholder = "Enter your email",
  ...props
}: FormProps) => {
  const [details, setDetails] = React.useState(false);

  const onClick = () => {
    setDetails(true);
  };

  return (
    <div className="form-wrapper">
      <input
        autoComplete="off"
        placeholder="Enter your email address"
        className={"storybook-input"}
        name="email"
        aria-label="email"
        id="email"
        data-testid="email"
        {...props}
      />
      <input
        autoComplete="off"
        placeholder="Enter your password"
        className={"storybook-input"}
        name="password"
        id="password"
        data-testid="password"
        {...props}
      />
      <Button label="Submit" onClick={() => onClick()} />
      {details === true ? (
        <p>
          Get ready to experience great development experience with storybook
        </p>
      ) : (
        ""
      )}
    </div>
  );
};

创建我们的CSS文件:

code src/stories/form.css
.form-wrapper {
  padding: 5rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 30%;
}

创建我们组件的初始状态

现在,父表单已经设置好了,是时候创建我们组件的初始状态了。让我们首先设置一个故事来设置表单的初始状态。

code src/stories/Form.stories.tsx
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { within, userEvent } from "@storybook/testing-library";
import { expect } from "@storybook/jest";
import { Form } from "./Form";

export default {
  title: "Example/Form",
  component: Form,
} as ComponentMeta<typeof Form>;

const Template: ComponentStory<typeof Form> = (args) => <Form {...args} />;

export const EmptyForm = Template.bind({});

export const InputedForm = Template.bind({});

在上面的代码块中,我们故事的基础部分已经建立:

Storybook empty form with no interactions

添加play 函数

现在是时候设置play 函数了,该函数在一个故事完成渲染后运行。通过play 函数,可以测试用户的工作流程。

// the play function comes into use
InputedForm.play = async ({ canvasElement }) => {
  // start querying the component from its root element
  const canvas = within(canvasElement);

  // the key part, where the interactions are defined
  // Simulate interactions with the component
  await userEvent.type(canvas.getByTestId("email"), "due@email.com");

  await userEvent.type(canvas.getByTestId("password"), "a-due-password");

  await userEvent.click(canvas.getByRole("button"));

  // assert DOM structure
  await expect(
    canvas.getByText(
      "Get ready to experience great development experience with storybook"
    )
  ).toBeInTheDocument();
};

通过上面的代码块,我们已经设置了交互测试。userEvent ,模拟用户与组件的交互,即填写表格和提交。

我们将依靠test-runner 来确认组件的渲染是否正确。更新你的package.json 脚本,启用测试运行器。

  "scripts": {
    "test-storybook": "test-storybook",
  },

用下面的命令运行测试运行器:

yarn test-storybook

Passes all yarn tests

在确认我们的测试通过后,我们可以导航到存放Storybook UI组件的浏览器窗口,观察交互测试的运行情况。

Interaction test in browser

Storybook中的交互测试类似于测试库的用户-事件API。如果你使用用户事件API,你就可以获得对浏览器交互进行高级模拟的能力,使用 [fireEvent](https://testing-library.com/docs/dom-testing-library/api-events#fireevent)方法。

总结

构建复杂的UI可能是一个困难的活动,特别是当开发是在多个团队成员之间进行的时候。但是用Storybook构建UI,它非常重视构建小的原子组件,这意味着组件可以被更有效地测试。

视觉测试是Storybook擅长的一个领域,有了交互测试的新工具,我们可以很容易地看到Storybook生态系统变得多么强大。