| 2022-04-09
我听过太多次无法忽视的抱怨,类似于:
我的 Selenium 测试不稳定!
自动化测试很不稳定。
它们有时成功,有时失败。
多么深刻的领悟,多么令人沮丧!
当这种状态持续一段时间后,大家就开始忽略自动化测试的结果了。
而这意味着,在发现 bug 和找到回归问题上,它们能为团队所提供的价值都将失去。
这是一种可耻的浪费,但并非必须如此。
首先,让我们明确澄清一下:
不是 Selenium 不稳定,是你的 Selenium 测试用例不稳定。
同样的说法,也适用于你用 WebDriver 写的测试用例。
当然,这提出了一个很明显的问题。
为什么 Selenium 测试有用,但却未能达到你的预期?
根据我的观察,导致不稳定测试用例的常见原因有很多,在这里与大家分享一下。
问题 1: 测试用例的隔离性差
场景
很多测试用例以相同的用户身份登录,而且还使用相同的固定数据集。
症状
单独运行某个测试用例时,它可以正常工作,
但在持续构建执行批量任务时,会「随机」失败。
解决方案
每个用例都尽可能使用隔离资源。在每个测试用例内组装数据,以避免依赖于构建中的「设置数据存储」步骤。 例如,使用Builder 模式。
为每个开发人员建立可独自使用的一个数据库。例如使用 Hypersonic 或 SQLite 之类的东西,作为私有的轻量级内存数据库。
创建隔离数据。如果你的应用程序需要用户登录,那么,就创建几个仅为你自己的测试用例保留的用户帐号,并提供锁定机制,以确保一次只有一个测试用例使用该特定的用户帐号。
问题 2:依赖不稳定的外部服务。
场景
使用生产环境的后端服务,或依赖团队自身无法控制的基础设施。
症状
由于相同的依赖原因(如后台的某个服务),可能导致所有测试用例都失败。
解决方案
- 不要依赖团队无法控制的外部服务。
这说起来容易做起来难,因为存在构建时间过长的风险,并且很难建立一个足够紧密地模拟服务且使测试有价值的环境。
有时在进程中启动服务也是可行的,例如使用 Java 世界中的 Jetty 或在 Ruby世界中使用 webrick 。
通过观察测试用例的执行是发现这些外部服务依赖的好方法。
在我们的一个项目中,尽管后端内容会发送回浏览器,但测试用例仍会时不时地超时。
通过观察发现了潜在的问题:我们提供的是「 fluff(来自 iframe 中外部服务的额外内容)」,而它有时无法及时加载。即使我们的测试用例不需要它,如果它尚未完成加载,我们的测试用例也会失败。
我们的解决方案是通过修改持续集成机器上的防火墙规则,来简单地阻止不必要的 fluff。
突然之间,一切都变得顺畅了一点!
- 在运行测试用例之前执行“健康检查”。即:对测试用例所依赖的所有服务「是否正常运行」进行事先检查。
鉴于端到端测试往往会运行很长时间,并且可能会给系统带来出乎意料的负载。
所以,这个方法不是万无一失的方法。但是,因检测出异常而跳过测试用例的执行,总比给团队一个「假失败」的结果要好。
问题 3:超时的设定时间不够长
场景
对于一个需要 15 秒才能完成的 AJAX 请求,你只等待了 10 秒。
症状
大多数情况下,测试运行良好,但在负载或特殊情况下它们就会失败。
解决方案
这里的困难点在于:由于异步性,我们无法提前就知道某个操作需要多长时间才能真正完成。
最明智的做法是:去检测(而不是等待)。
去检测所要的操作是否已完成,以便测试用例可以尽快地继续执行下去。 而不仅仅是设置一个等待时长,然后一直等着这个时间点到期而 Time Out。
例如,当 XmlHttpRequest 返回时,更改生产代码以在 Javascript 的`Window` 对象上的全局设置标志并不难,这就构成了简单闩锁的基础。
你可以设置一个等待标志,而不是轮询 UI。 或者,如果你的 UI 给出明确的「操作忆完成」的信号,就请使用轮询。
Selenium RC 和 WebDriver 等框架提供了帮助类,使这变得更加容易。
问题 4:超时的设定时间太长
场景
通过轮询一段文本来等待页面加载完成,只是让服务器抛出异常并给出 500 或 404 错误,而文本永远不会出现。
症状
你的测试不断超时,可能让你的构建任务也持续超时。
解决方案
不要只轮询你想要的结束条件,还要考虑轮询众所周知的错误条件。
当你看到错误条件时,让测试失败并显示信息性错误消息。
由于这个原因,WebDriver 的 SlowLoadableComponent 有一个“isError”方法。你可以为 Selenium RC 将附加检查放在一个正常的 Wait里。
组织一个特攻队
当你的测试用例不稳定时,请进行一些根本原因分析,以了解它们为何不稳定。
为了有效地完成此类分析和测试稳定性改进工作,你可能需要其它团队的支持和帮助。 假如你是自己工作,或者在一个小团队中工作,这可能不会太难。但在在大型项目中,这可能会比较难。
所以,让一两个人不去开发功能,而是让测试运行更稳定,会让你的项目取得成功。
你可能会想,这么做会导致编写生产代码的人力减少,团队会有生产力损失。
但从长期看,还是值得的。
因为这能让端到端的测试套件更稳定且有效,即:一旦「测试用例失败」,就是真正的「失败」。