返回博客

测试用户流程

验证您的 UI 端到端工作

loading
Varun Vachhar
@winkerVSbecks
最后更新

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

UI 帮助人们在多个页面上导航一系列步骤以完成他们的目标。 Storybook 使隔离每个页面并对其运行可视化可访问性交互测试变得容易。但是要验证整个流程并捕获集成问题,您需要端到端 (E2E) UI 测试。

我研究了 Peloton、Shopify、O'Reilly 等十个领先的前端团队,以了解他们如何应用 E2E 测试来检查用户流程。本文总结了我的发现。您还将了解所涉及的工具以及 E2E 测试如何融入您的 UI 测试策略。

您的 UI 是否端到端工作?

用户流程不只包含一个组件,它们涉及多个组件协同工作。每次交互都会触发状态更新、路由更改和 API 调用,这些都会影响屏幕上呈现的内容。由于存在所有这些故障点,因此逐个 QA 它们可能很困难。

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

应用程序是通过将组件连接到数据、业务逻辑和 API 来组装的

测试完整的应用程序会带来权衡

从表面上看,E2E 和交互测试看起来非常相似。但请记住,您的用户除了与其组成组件之外,还会与应用程序进行交互。 E2E 测试在应用程序级别运行,这使它们能够发现前端和后端之间的集成问题。但这还需要您为技术堆栈的更多层维护测试基础设施(耗时!)。

组件级测试由可以挂载、渲染和测试组件的独立工具完成。对于 E2E 测试,您负责启动应用程序。为此,您有两个选择

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

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

鉴于这种权衡,大多数团队使用混合方法来平衡工作量和价值。 E2E 测试仅限于关键用户流程。交互测试用于验证所有其他行为。

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

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

教程

让我们看看在之前的文章中介绍的 Taskbox 应用程序的工作流程操作。我们将为身份验证流程编写 E2E 测试:导航到登录页面并填写用户凭据。身份验证后,用户应该能够看到他们的任务列表。

获取代码并通过运行 yarn start 在开发模式下加载应用程序。然后打开https://127.0.0.1:3000,您将看到登录屏幕。

设置 Cypress

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

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

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

// cypress.json
{
 "baseUrl": "https://127.0.0.1:3000"
}

最后,运行 yarn cypress 以完成设置过程。这将在您的项目中添加一个 cypress 文件夹。所有测试文件都将位于此处。它还将启动 Cypress 测试运行器。

测试身份验证流程

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

// cypress/e2e/auth.spec.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"] li').should('have.length', 6);
  });
});

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

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

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

但是,请注意测试失败了。那是因为我们只运行应用程序的前端。由于我们没有活动的后端,所有 HTTP 请求都将失败。我们将使用存根网络请求而不是启动实际的后端。

模拟请求

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

之前的文章中,我演示了如何使用 Storybook 编目组件的所有用例。在该过程中,我们在 TaskList.stories.js 文件中为任务列表创建了模拟数据。我们可以在 Cypress 测试中重用它。

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

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

// cypress/e2e/auth.spec.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"] li').should('have.length', 6);
  });
});

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

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

结论

用户体验是通过组合组件并将它们连接到数据和 API 来组装的。 Storybook 使在组件级别测试它们变得容易。您将用例捕获为 stories,并使用 Jest 和 Testing Library 执行断言。这些测试易于维护并提供快速反馈循环。但是,它们不允许您测试跨越多个页面的流程。

另一方面,E2E 测试在应用程序的整个实例上运行。您可以测试用户流程并验证系统的所有层是否按预期工作。缺点是该过程需要大量时间和精力。

鉴于这种权衡,大多数团队将自己限制为一小部分针对核心用户流程的 E2E 测试。并将组件级测试用于更广泛的覆盖范围。

只有当您持续运行测试时,测试才有用。领先的工程团队使用持续集成 (CI) 服务器来运行他们的完整测试套件——在每次代码推送时自动运行。下一篇文章将回顾完整的 UI 测试策略,并向您展示如何自动化该工作流程。加入邮件列表以获取更多 UI 测试文章发布时的通知。

加入 Storybook 邮件列表

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

6,730位开发者及更多

我们正在招聘!

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

查看职位

热门文章

如何使用 Github Actions 自动化 UI 测试

加快您的工作流程并交付更高质量的代码
loading
Varun Vachhar

UI 测试手册

一个不会减慢您速度的测试工作流程
loading
Varun Vachhar

交互测试抢先看

使用 Storybook 的 play 函数测试连接的组件
loading
Dominic Nguyen
加入社区
6,730位开发者及更多
为什么为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与其中博客
案例探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI