编写 E2E 测试时要考虑什么#frontend@twiliosendgrid

已发表: 2020-09-19

在 Twilio SendGrid,我们在新功能或页面开发周期结束时编写端到端 (E2E) 测试,以确保从最终用户的角度来看,前端和后端之间的所有部分都已连接并正常工作。

我们已经尝试了各种 E2E 测试框架和库,例如我们自己的自定义内部 Ruby Selenium 框架、WebdriverIO,主要是 Cypress,如博客文章系列的第一部分第二部分中强调的那样,记录了我们在所有领域的迁移解决方案。 无论我们使用什么框架或库,我们发现自己都在问同样的问题,即我们可以自动化哪些功能并为其编写 E2E 测试。 在确定我们可以测试哪些功能之后,我们还注意到自己一遍又一遍地应用相同的通用策略来编写和设置测试。

这篇博文不需要任何在某个库或框架中编写 E2E 测试的先验知识,但是如果您已经看过 Web 应用程序并想知道如何在浏览器中最好地自动化操作以测试页面是否正常工作,它会有所帮助。 我们旨在引导您了解如何考虑 E2E 测试,以便您可以将这些问题和编写测试的一般策略应用于您可能选择的任何框架。

询问您是否可以自动化 E2E 测试的问题

在编写 E2E 测试时,我们需要确保我们在应用程序中测试的页面中的流程符合某些标准。 让我们来看看我们问自己的一些高级问题,以确定 E2E 测试是否可以自动化。

1、是否可以通过API等可靠的方式在每次测试前将用户数据重置回某个状态? 如果没有办法可靠地将用户重置回您想要的状态,则它无法自动化,并且无法在部署之前作为阻塞测试的一部分运行。 通过 UI 将用户返回到某个状态也是一种反模式并且通常是不确定的,因为它很慢,并且通过 UI 的自动化步骤已经足够不稳定了。 无需在浏览器中打开页面即可进行 API 调用以重置用户状态更为可靠。 另一种选择是,如果您有一项服务存在,则在每次测试之前使用适当的数据创建新用户。 只要我们在每次测试之前重置一个持久用户或创建一个用户,我们就可以专注于我们在页面上测试的部分。

2. 我们是否可以控制我们打算测试的功能、API 或系统? 如果它是您依赖于计费或任何其他功能的第三方服务,有没有办法将它们模拟出来或让它在某些值下确定性地工作? 您希望尽可能多地控制测试以减少片状。 您可以在每次测试运行时创建具有隔离资源或数据的专用测试用户,这样它就不会受到其他任何东西的影响。

3. 服务或功能本身是否足够一致,可以在合理的超时时间内工作? 通常,您可能必须实施轮询或等待某些数据被处理并进入数据库(例如较慢的异步更新和触发的电子邮件事件)。 如果这些服务在合理且可靠的时间窗口内频繁发生,您可以在等待特定 DOM 元素显示或数据更新时设置适当的轮询超时。

4. 我们可以在页面上选择我们需要交互的元素吗? 您是否正在处理您无法控制且无法更改的 iframe 或生成的元素? 为了与页面上的元素进行交互,您可以添加更具体的选择器,例如 `data-hook` 或 `data-testid` 属性,而不是选择 id 或类名。 id 和类名更容易更改,因为它们通常与样式相关联。 想象一下,尝试从样式组件或 CSS 模块中选择散列的类名或 id。 对于第三方生成的元素或开源组件库(如 react-select),您可以将这些元素与具有 `data-hook` 属性的父元素包装在一起,并选择下面的子元素。 为了处理 iframe,我们创建了自定义命令来提取我们需要断言和操作的 DOM 元素,稍后我们将提供一个示例。

需要考虑的因素更多,但归结为一个问题:我们能否以一致且及时的方式重复此端到端测试并获得相同的结果?

编写 E2E 测试的一般策略

1.找出我们可以自动化的高价值测试用例 一些示例包括涵盖大部分功能流的快乐路径测试:通过 UI 执行 CRUD 操作以获取用户的个人资料信息、过滤表格以匹配给定数据的结果、创建帖子或设置 API 密钥。 然而,其他边缘情况和错误处理可能更好地包含在单元和集成测试中。 通过我们在上一节中提到的问题运行它,以帮助缩短您的测试用例列表。

2.考虑如何通过API尽可能地设置或拆除来重复这些测试。 对于高价值、可自动化的测试用例,开始注意应该通过 API 设置哪些内容。 如果用户没有足够的可过滤数据进行分页,如果用户的数据在 30 天的滚动窗口到期,或者如果我们可能需要删除一些成功或不完整留下的数据,则一些示例为用户提供适当的数据在当前测试再次开始之前进行测试。 无论最后一次测试如何成功或失败,测试都应该能够运行并将其自身设置为相同的可重复状态。

重要的是要思考:我如何才能将此用户的数据重置回起点,以便仅测试我想要的部分功能?

例如,如果您想测试用户添加帖子以使其最终显示在用户的帖子列表中的能力,则必须首先删除该帖子。

3. 设身处地为客户着想,跟踪完全完成功能流程所需的 UI 步骤。 记录客户完成完整流程或操作的步骤。 在每个步骤之后跟踪用户应该或不应该看到或与之交互的内容。 我们将在此过程中进行完整性检查和断言,以确保用户遇到正确的事件序列以执行他们的操作。 然后,我们会将健全性检查转换为自动命令和断言。

4. 通过添加特定的选择器和实现页面对象(或任何其他类型的包装器)来维护更改和自动化流程。 查看您写下的那些步骤,以了解如何操作和完成功能流。 向用户交互的元素(如按钮、模式、输入、表格行、警报和卡片)添加更具体的选择器,如 `data-hook` 属性。 如果您愿意,您可以创建页面对象、包装器或帮助文件,并通过您添加的选择器引用这些元素。 然后,您可以实现可重用的函数来与页面的可操作元素进行交互。

5. 使用您创建的助手将您记录的用户步骤翻译成赛普拉斯测试。 在测试中,我们通常通过 API 登录用户,并在每个测试用例运行之前保存会话 cookie 以保持登录状态。然后我们通过 API 设置或删除用户的数据,以获得一致的起点。 一切就绪后,我们将访问我们将直接测试我们的功能的页面。 我们继续执行流程的步骤,例如创建、更新或删除流程,断言在此过程中应该发生什么或在页面上可见。 为了加快测试速度并减少不稳定,避免通过 UI 重置或建立状态,并绕过通过登录页面登录或通过 UI 删除内容等操作,以专注于您想要测试的部分。 确保始终在 `before` 或 `beforeEach` 钩子中执行这些部分。 否则,如果您使用 `after` 或 `afterEach` 钩子,测试可能会在两者之间失败,导致您的清理步骤永远不会运行并导致后续测试运行失败。

6. 锤击并消除测试薄片。 在实施了测试并在本地通过了几次之后,很容易设置一个拉取请求,立即合并它,并让测试与您的测试套件的其余部分按计划运行,或者在您的部署步骤中触发它们。 在你这样做之前:

    1. 首先,尝试让用户处于各种状态,看看您的测试是否仍然通过,以确保您有正确的设置步骤。
    2. 接下来,调查在您的一个部署流程期间触发时并行运行您的测试。 这使您可以查看资源是否被相同的用户踩踏,以及是否发生了任何竞争条件。
    3. 然后,观察您的测试如何在 Docker 容器中以无头模式运行,看看您是否可能需要增加任何超时或调整任何选择器。

目标是了解您的测试在不同条件下的重复测试运行中的表现如何,并使它们尽可能稳定和一致,以便我们花更少的时间返回修复测试并更多地专注于捕捉环境中的实际错误。

这是一个 Cypress 测试样板布局示例,我们在其中创建了一个名为“cy.login(用户名,密码)”的登录全局支持命令。我们明确设置 cookie 并将其保存在每个测试用例之前,这样我们就可以保持登录状态并直接进入到我们正在测试的页面。 我们还通过 API 进行一些设置或拆卸,每次都绕过登录页面,如下所示。

结束思想

除了比较哪种 E2E 解决方案最适合使用之外,采用正确的 E2E 测试思维方式​​也很重要。 首先询问有关您要测试的功能是否符合自动化要求的问题至关重要。 应该有办法让您可靠地将用户或数据重置回某个状态(例如通过 API),这样您就可以专注于您要验证的内容。

如果没有可靠的方法将您的用户或数据重置到正确的起点,您应该考虑构建工具和 API 来创建具有特定配置的用户。 你也可以考虑模拟出你可以控制的东西,以使测试尽可能稳定和一致。 否则,您应该考虑与您的团队的价值和权衡。 当推送新的代码更改时,您应该将此功能留给单元测试或手动回归测试吗?

对于可以在 E2E 测试中自动化的那些功能,覆盖用户的主要快乐路径流程通常是最有价值的。 再一次,在您使用您想要的任何框架或库编写 E2E 测试时,以及时和一致的方式使事情可重复,并消除脆弱性。

有关具体赛普拉斯 E2E 测试的更多信息,请查看以下资源:

  • 编写柏树测试的 1,000 英尺概述
  • TypeScript 包含 Cypress 测试中的所有内容
  • 在 Cypress 测试中处理电子邮件流
  • 配置、组织和整合赛普拉斯测试的想法
  • 将 Cypress 测试与 Docker、Buildkite 和 CICD 集成