关于自动化测试中的反模式

关于自动化测试中的反模式

dfsfds

自动化测试在软件研发中应用得已越来越广泛,自动化测试框架也变得越来越强大、可扩展,而且可以得到AI的辅助。然而,至今仍然存在各种相似的失败在各个团队间频繁出现:不稳定的测试、缓慢的调度、不稳定的定位符和脆弱的架构

而这些问题其实很少来自于工具本身。通常,它们源于在项目中反复出现的一些特定的反模式

因为自动化不仅仅是编写测试,它也关系到编写良好的测试设计测试架构考虑可维护性以及做出长期决策。但一些测试人员仍然有这些糟糕的习惯,并逐渐破坏了测试框架的可靠性。

以下我们逐步讨论一些最具破坏性的自动化反模式,并谈谈它们出现的原因、造成的影响以及如何修复。

一次性覆盖所有内容

工作中,大家应该都见过或做过这种情况:开发一个巨大的测试脚本,并试图涵盖整个用户行为路径。

这看起来没什么问题,也觉得覆盖了很多场景。但实际上,这是最大的自动化陷阱之一。

这种测试通常包括:

  • 登录
  • 在多个页面间导航
  • 创建数据
  • 编辑数据
  • 删除数据
  • 验证多个 UI 部分
  • 登出

就好像一个线性的流水线,如果某一步失败,整个场景就崩溃了。

这些测试是:

  • 极其脆弱——一个按钮的变化可能会使整个测试套件失效。
  • 难以调试——你花费更多时间在调查而非修复上。
  • 速度慢——迫使我们的 CI 管道缓慢运行。
  • 误导性——它们给人一个虚假的覆盖感,却忽视了细节。

比如下面的例子:

1
2
3
4
5
6
7
it("应该测试整个用户旅程", async () => {
  await loginPage.login();
  await users.createUser();
  await dashboard.checkWidgets();
  await profile.updateInfo();
  await users.deleteUser();
});

这个测试做了所有事情,但这正是问题所在。

要规避它的问题,我们需要做到:

  • 每个测试应专注于一个特定的行为。
  • 设置/清理应通过 API 或数据库完成,而不是通过 UI。
  • 不要在一个场景中测试所有内容——独立测试每一部分。

这将降低复杂性,加快执行速度,并使自动化更加稳定。

随意选择定位符(比如第一个可用的)

UI 不稳定的最大来源之一是选择了错误的定位符。

很多自动化测试编写者,会抓取第一个出现的 XPath 或 CSS 选择器,将其粘贴到代码中,然后继续。这可能在最初有效,但问题是“有效”的第一个定位符往往是最脆弱的。

比如,一些不良实践可能包括:

  • 15 层的完整 XPath
  • 使用动态生成的 ID
  • 使用与样式相关的庞大 CSS 选择器
  • 使用每个 Sprint 都在变化的基于文本的定位符
  • 使用基于索引的元素,如 (//button)[3]

一个例子:

1
await $('//*[@id="app"]/div/div[2]/div[1]/div[3]/span[2]');

如果设计师移动了一个 div,那么整个case就会崩溃。

选择第一个有效的定位符是糟糕的,因为:

  • 测试变得不稳定。
  • 每次小的 UI 变动都会引发一系列失败。
  • 我们的自动化需要不断维护。
  • 新成员很难理解正在测试什么。

要解决这个问题,我们需要使用稳定、可读且长期使用的定位符,推荐的包含:

  • data-testid
  • 可访问性 ID
  • 短小且有意义的 ID
  • 可预测的 CSS 类

测试脚本开发者应该时刻与开发人员合作,改进可测试性,因为良好的自动化始于一个可测试的产品。

基于复制-粘贴的测试框架

这种情况比人们承认的更经常出现。某个 QA 急于交付自动化,因此在 1 到 2 天内草草准备一个框架,然后就立即开始编写测试。

起初,这看似“快速且高效”,但几个月后将演变为一场噩梦。

这种方法的结果通常是:

  • 测试中重复代码
  • 没有配置文件
  • 脚本中硬编码的环境 URL
  • 没有文件夹结构
  • 相同的选择器和函数重复 20 次
  • 没有可重用组件
  • 没有抽象层

比如:

1
2
3
4
5
await browser.url("https://staging.myapp.com");
await $('#email').setValue("user");
await $('#pass').setValue("123");
await $('#submit').click();
//(这在 40 个不同测试中被复制粘贴)

某天,登录按钮发生了变化——突然间 40 个测试失败。你需要在 40 个测试中进行修改。多么可怕的维护量!

这种方法的糟糕之处在于:

  • 维护变得异常缓慢。
  • 小的 UI 变动需要巨大重构。
  • 新的 QA 无法快速上手。
  • 测试在风格、逻辑和稳定性上变得不一致。

为了避免这种噩梦,我们需要建立一个坚实的基础,以节省后面几个月的工作。我们需要投资于一个适当的自动化架构:

  • 清晰的文件夹结构
  • 页面对象模型或剧本模式
  • 集中等待
  • 全局配置和环境变量
  • 可重用的助手
  • 日志记录、报告和错误处理

“wait”的过度使用

这种情况也很常见

测试失败了吗?
让我们加 5 秒。
仍然失败?
让我们加 10 秒。
仍然失败?
为了保险,我们加 20 秒。

经典例子

1
await browser.pause(15000);

突然间,整个测试套件每天要多花 30 分钟。

这是一种非常糟糕的做法,因为:

  • 它大幅增加了执行时间。
  • 使测试的通过更多是侥幸而非准确。
  • 隐藏了应用中的真实性能问题。
  • 使调试变得更加困难。
  • 产生了对测试稳定性的虚假信心。

我们应始终等待某个条件,而不是无所事事地等待。要修复这个反模式,我们需要使用:

  • 显式等待
  • 基于条件的等待
  • 元素状态检查
  • 使用 API 轮询而非 UI 等待
  • 现代 AI 框架的视觉等待

自动化一切

100% 的自动化覆盖绝对不应该是我们的目标。有些场景的测试变动过于频繁,稳定性不足,复杂而无法自动化,或者没有真正的业务价值,或者需要真实的人工判断。这些场景不应纳入我们的自动化测试套件。

此外,还有一些场景不应该进行自动化测试:

  • 针对动画的 UI 测试
  • 快速变化的 UI 屏幕测试
  • 非常深度的探索场景
  • 一次性的罕见特性流
  • 具有不一致的外部依赖的测试

如果你自动化这些场景,你将:

  • 浪费时间自动化不断失败的测试
  • 开始维护测试,而不是提高质量
  • 得到虚假正面和虚假负面
  • 花费数天修复无关紧要的测试

因此,在开始自动化某个场景之前,我们应问自己这四个简单问题:

  • 测试是否稳定?
  • 这个测试在三个月后还相关吗?
  • 它节省了手动工作吗?
  • 对于业务来说它是关键的吗?

如果答案是否定的——保持手动测试

通过 UI 而非 API 准备数据

一些测试团队通过 UI 准备所有测试数据。这包括创建用户、上传文件、更新设置、设置环境状态等。每一个这样的步骤都会让我们的 UI 测试变得更慢、更脆弱。

这不是一个好的做法,因为 UI 是最慢和最脆弱的层次。此外,通过 UI 进行这些操作会引入不必要的步骤,从而增加测试的不稳定性。API 或数据直接通过数据库准备会更简单、更快速。

为避免此问题,好的做法是:

  • 通过 API 创建测试数据
  • 通过 API 或数据库清理
  • 仅在实际行为验证时使用 UI

忽视不稳定测试

我们已经讨论过不稳定测试。它们通常存在于不稳定的定位符、错误的等待、不一致的环境、UI 中的竞争条件、破损的测试数据设置或第三方依赖之中。

每当我们注意到不稳定测试时,不应忽视它们。我们应该将不稳定性视为 P1 级别的错误。

一旦我们发现了不稳定的测试,我们应:

  • 跟踪每个测试的不稳定性
  • 使用仪表板或标签监控不稳定测试
  • 关注根本原因
  • 不依赖重试来“隐藏”问题

没有稳定性的自动化不是自动化——而是混乱。

仅在一个浏览器或设备上测试

如果我们只在 Chrome 上运行所有测试,只在一个 Android 设备上,或者只在一个 iOS 模拟器上,这就不够了,因为用户无处不在。用户使用 Safari、不同的 iPhone、可折叠的 Android 设备、高分辨率平板电脑、深色模式等。

如果我们仅使用一个设备,则Bug 可以出现在自动化未触及的设备上,浏览器特定的 Bug 可能会溜入生产环境,布局问题可能会在真实设备上出现,而我们的自动化测试可能会给我们带来虚假的安全感。

为避免此问题,我们应始终根据产品的需求创建测试矩阵,例如:

  • Chrome + Safari(最少)
  • 一台真实的 Android 设备
  • 一台真实的 iPhone
  • 明亮模式 + 深色模式
  • 至少 2 到 3 种屏幕大小

这能早点发现 UI 问题并提升信心。

依赖 CI 而不在本地运行任何东西

我们不应将 CI 管道作为个人调试环境,不应直接将代码推送到管道并寄希望于最好的结果。

这不是一个好的做法,因为它可能会阻塞整个管道,破坏主分支,减慢整个团队的速度,并且会使调试更加困难,因为 CI 所需时间更长。

为避免这些问题,我们应始终:

  • 在将更改推送到管道之前,确认本地测试可正常运行。
  • 在本地运行小部分测试
  • 将完整回归测试留给 CI,但自己测试基础功能

没有日志、没有截图、没有跟踪

我们的测试应提供丰富的结果信息,而不仅仅是“通过/失败”的状态。一些框架不保存截图、不捕获控制台日志、不录制视频、不收集网络跟踪,不会生成清晰报告,使调试更加困难和复杂。

我们应始终研究我们框架的可能性,也许使用一些额外的插件、HTML 报告器或第三方工具来生成这些对我们测试结果有价值的信息。这些信息对我们 QA 来说非常重要,对整个团队和其它利益相关者也是如此。

结语

如你所见,这些例子中,最大的自动化失败通常是由快速决策、不良习惯和缺乏结构导致的。

通过避免这些反模式,我们的自动化测试套件将更加稳定、可维护、快速、可靠,并受到团队的信任。

自动化需要规划和纪律,而不仅仅是工具。

使用 Hugo 构建
主题 StackJimmy 设计