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

测试用户流程

验证您的 UI 是否端到端工作

在生产环境中调试是场噩梦。您必须检查应用中的每一层。是组件错误、事件触发失误、样式问题、应用状态问题,还是 API 损坏?可能是以上任何一种,您必须理清其中的原因。

UI 帮助用户跨多个页面按顺序完成一系列步骤以达成目标。到目前为止,我们已经看到了 Storybook 如何轻松地隔离每个页面并对其运行可视化可访问性交互测试。但要验证整个流程并捕获集成问题,您需要端到端 (E2E) UI 测试。

您的 UI 是否端到端工作?

用户流程不仅仅局限于一个组件,它们涉及多个组件协同工作。每次交互都会触发状态更新、路由更改和 API 调用,从而影响屏幕上的渲染内容。由于有这么多潜在的故障点,一一进行 QA 可能很困难。

团队使用 E2E 测试来确保用户体验按预期工作。运行 E2E 测试时,您首先启动一个完整的应用实例。然后使用 CypressPlaywrightSelenium 等工具,通过模拟用户行为来验证用户流程。

应用通过连接组件与数据、业务逻辑和 API 来组装

测试完整的应用伴随权衡

表面上看,E2E 和交互测试非常相似。但请记住,您的用户与整个应用交互,而不仅仅是 UI 组件。E2E 测试在应用层面运行,这使得它们能够发现前端和后端之间的集成问题。但这还需要您为技术栈的更多层级维护测试基础设施(耗时!)。

组件层面测试由独立的工具完成,这些工具可以挂载、渲染和测试组件。使用 E2E 测试时,应用实例的启动由您负责。您有两种选择:

  1. 维护一个完整的测试环境:这包括前端、后端、服务以及种子测试数据。例如,O'Reilly 团队使用 Docker 启动整个应用基础设施并运行 E2E 测试。
  2. 维护一个仅包含前端的测试环境,并搭配一个模拟后端。例如,Twilio 使用 Cypress 模拟网络请求来测试流程。

无论哪种方式,复杂性都会随着系统规模的扩大而增加。系统越大,在持续集成服务器上复制设置并连接到云端浏览器运行测试就越麻烦。

考虑到这种权衡,大多数团队采用混合方法来平衡投入和价值。E2E 测试仅限于关键用户流程,而交互测试用于验证所有其他行为。

在本教程中,我们使用模拟后端方法,通过 Cypress 进行 E2E 测试。以下是工作流程摘要:

  1. ⚙️ 设置:启动应用并模拟网络请求(重用故事中的数据)
  2. 🤖 操作:使用 Cypress 访问页面并模拟交互
  3. 运行断言以验证 UI 是否正确更新

教程:测试身份验证流程

我们将为身份验证流程编写一个 E2E 测试:导航到登录页面并填写用户凭据。身份验证成功后,用户应该能够看到他们的任务列表。

login page inbox page

运行 yarn dev 以开发模式启动应用。然后打开 http://localhost:5173,您将看到登录屏幕。

设置 Cypress

运行 yarn add --dev cypress 安装 Cypress 包。然后将 Cypress 命令添加到 package.json 文件的 scripts 字段。

{
  "scripts": {
    "cypress": "cypress open"
  }
}

接下来,在项目根目录添加一个 cypress.config.js 文件。我们可以在这里配置应用的基 URL,这样在编写实际的测试命令时就不必重复了。

复制
cypress.config.js
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:5173/',
    supportFile: false,
  },
});

最后,运行 yarn cypress 完成设置过程。这将在您的项目根目录添加一个 cypress 文件夹,所有测试文件都将存放在这里。它还会启动 Cypress 测试运行器。

测试身份验证流程

Cypress 测试结构与其他您可能熟悉的测试类型非常相似。首先,您描述要测试的内容。每个测试都位于一个 it 块中,您可以在其中运行断言。以下是身份验证用户流程测试的示例:

复制
cypress/e2e/auth.cy.js
describe('The Login Page', () => {
  it('user can authenticate using the login form', () => {
    const email = 'alice.carr@test.com';
    const password = 'k12h1k0$5;lpa@Afn';

    cy.visit('/');

    // Fill out the form
    cy.get('input[name=email]').type(email);
    cy.get('input[name=password]').type(`${password}`);

    // Click the sign-in button
    cy.get('button[type=submit]').click();

    // UI should display the user's task list
    cy.get('[aria-label="tasks"] div').should("have.length", 6);
  });
});

我们来分解一下这里发生的事情。cy.visit 打开浏览器到应用的登录页面。然后我们使用 cy.get 命令找到并填写电子邮件和密码字段。最后,点击提交按钮实际登录。

测试的最后一部分运行断言。换句话说,我们验证身份验证是否成功。我们通过检查任务列表是否可见来做到这一点。

切换到 Cypress 窗口,您应该会看到测试正在执行。

但是,请注意测试失败了。这是因为我们只运行了应用的前端。所有 HTTP 请求都会失败,因为我们没有活跃的后端。我们将使用模拟的网络请求来代替启动实际的后端。

模拟请求

cy.intercept 方法允许我们拦截网络请求并用模拟数据响应。身份验证用户流程依赖于两个请求:/authenticate 用于登录,/tasks 用于获取用户的任务。要模拟这些请求,我们需要一些模拟数据。

组合测试章节中,我们为任务列表故事创建了模拟数据。现在我们将在 Cypress 测试中重用这些数据。

复制
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',
      },
    ],
  },
};

让我们继续更新测试以模拟这两个网络请求。

复制
cypress/e2e/auth.cy.js
+ import { Default as TaskListDefault } from '../../src/components/TaskList.stories';

describe('The Login Page', () => {
+  beforeEach(() => {
+    cy.intercept('POST', '/authenticate', {
+      statusCode: 201,
+      body: {
+        user: {
+          name: 'Alice Carr',
+          token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9',
+        },
+      },
+    });
+
+    cy.intercept('GET', '/tasks', {
+      statusCode: 201,
+      body: TaskListDefault.args,
+    });
+  });

  it('user can authenticate using the login form', () => {
    const email = 'alice.carr@test.com';
    const password = 'k12h1k0$5;lpa@Afn';

    cy.visit('/');

    // Fill out the form
    cy.get('input[name=email]').type(email);
    cy.get('input[name=password]').type(`${password}`);

    // Click the sign-in button
    cy.get('button[type=submit]').click();

    // UI should display the user's task list
    cy.get('[aria-label="tasks"] div').should("have.length", 6);
  });
});

重新运行测试,现在应该可以通过了。

我们启动了整个应用,模拟了用户行为,并使用 Cypress 测试了登录流程。在这个测试中,我们检查了数据流、表单提交和 API 调用。

自动化 UI 测试

测试只有持续运行才有价值。领先的工程团队使用持续集成 (CI) 服务器来运行他们的完整测试套件——在每次代码推送时自动运行。我们已经介绍了五种不同类型的测试,下一章将向您展示如何自动化它们的执行。

让您的代码与本章节保持同步。在 GitHub 上查看 74eeff9。
这份免费指南对您有帮助吗?发推文点赞并帮助其他开发者找到它。
下一章
自动化
加速您的工作流程,并交付更高质量的代码
✍️ 在 GitHub 上编辑 – 欢迎提交 PR!
加入社区
6,975开发者及更多
原因为何选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI