成功并不是一蹴而就的,在找到发力点之前,我们不得不跌跌撞撞地经历了多次尝试。让我们一起回顾一下:这是怎么开始的,在哪里挣扎过,最后又是如何柳暗花明的。
我们启动了一个名为"Project Cornflake"的项目,目标是自动检测和隔离不稳定的测试。
首先,需要弄清楚不稳定的测试和失败的测试之间的关系。
- 失败的测试是指在多次重试中持续失败的测试用例。
- 不稳定的测试是指在多次重新运行中最终能够通过的测试用例。
第一种方案,在测试用例不稳定率触及某一阈值时,自动过滤不稳定测试用例的结果。
我们打算开发一个系统,可以自动检测不稳定的测试用例,并根据历史记录计算测试用例的不稳定概率。如果一个测试用例超过了可容忍的阈值,就把这个用例从测试结果中过滤掉,这样它就被隔离了,之后不会出现由于这个用例执行失败而使得测试任务失败。
测试失败率会根据不同的测试类型而变化。例如,单元测试比功能测试出错率低,而功能测试和E2E测试相比更不容易出错。考虑到这一点,我们决定建立一个基于不同测试类型的解决方案:给每个测试类型定义不同的失败容忍度。
1. 具体实施方案
针对每个CI流水线中失败的测试任务,我们做了以下工作:
-
- 通过解析测试结果识别出失败的测试用例。
- 对于每个失败的用例,从后台拉出过去N次测试运行中该用例在主干分支上被执行的历史记录(例如N=50)。
- 计算每个失败用例的不稳定率,即测试运行失败的次数/N的结果。
- 如果一个测试用例没有任何历史记录或没有足够的历史记录,那么它将自动被认为是不稳定的测试(也就是说,我们首先假设一个测试用例是不稳定的,直到被证明是稳定的)。
- 从测试结果中删除超过给定阈值的不稳定的测试用例。
- 在后台给这个用例设置 is_flaky = true 来标注这是一个不稳定的用例。
- 修改CI流水线中的xml测试结果文件,把测试任务标记为绿色(通过)。
图1 方案一:在触发阈值时自动隔离不稳定测试用例的结果
2. 实施效果
我们观察到,自项目推出以来,PR分支和主干分支测试任务的稳定性都很快得到了提升。PR分支构建的稳定性在一周之后就从71%提高到了88%(如图2所示)。主干分支的构建稳定性在一周内从61%提高到90%(如图3所示)。
图2 iOS PR分支上单元测试稳定性
图3 iOS主干分支测试稳定性
3. 方案的弊端
虽然该方案在最初推出时效果很好,但我们逐渐发现越来越多的问题:如果测试任务失败了,很难调查清楚。我们决定放弃这个方案,最主要的原因是因为在这个方案中,不稳定的用例会流入到主干分支上。
弊端1:不稳定的测试变成了失败的测试,并流入到主分支中。
来看看下面的情况。
-
- 开发人员 A 写了一个新的测试用例,并合并到主分支。
- 由于该用例在后台没有足够的执行结果数据,它被自动归类为不稳定的测试,并且测试任务结果中会把它过滤掉,标记测试任务为成功。
在主分支上,如果该用例运行失败,但由于缺乏足够的测试执行历史记录,虽然在主干分支上会一直运行它,但在CI上会从测试结果中把它过滤掉。如果这条测试用例在开发人员本地执行时失败,但包含这条用例的CI构建任务是通过的,就会让开发人员感到困惑。而一旦这条用例积累了足够的执行数据,并且超过了容忍阈值,CI中的测试任务才开始被标记成失败。到这时候,开发人员或排查人员不得不从最初失败的时候开始调查,这就违背了这个改进项目的初衷。
弊端2:不稳定的测试用例会被追加到测试集中。
试想一下下面的情况。
-
- 开发人员A创建一个PR修复一个不稳定的测试用例,但是没有修复成功,测试用例仍然运行失败。
- 在这种情况下,CI 任务应该是失败的,因为没有从结果中把这条用例的结果移除。
- 这时,系统查看历史记录,找到了足够的数据来计算该用例不稳定的百分比,并从结果中删除这条用例,将其归类为不稳定测试并且测试任务标记为通过。
- 开发人员查看工作状态,看到✅,以为测试用例已经修复好,于是关闭了Jira问题单。但事实上,这条用例仍然是不稳定的。
弊端3:对后台的依赖性。
本地开发:测试执行记录是存储在系统后台的,这意味着本地开发者在运行测试时的体验是不同的。将系统同步到本地开发者环境的工作量很大。如果后台出现故障,CI测试任务就会中止。测试任务将由于 "无法计算不稳定的测试 "而失败。这将导致开发人员无法合并他们的代码,或者主构建任务无法更新到后台。
4. 得到的启示
- 简单地过滤掉不稳定的测试用例并不是最好的方法,因为我们只是靠隐藏测试失败来避免对整个构建任务的稳定性造成影响(译者注:像是掩耳盗铃)。这个方案让针对不稳定测试的调查工作变得困难,因为没有足够的信息说明测试状态是何时改变的。
- 与其在PR分支和主干分支都来进行测试失败检测和隔离,不如只在主分支上隔离测试。这样一来,开发者就可以简单地拉出最新的主干分支来避免他们的PR分支上被引入不稳定的测试。
于是,我们决定采用第二种方案:停止执行不稳定的测试用例,而不是在执行完之后对结果进行过滤。只要一次执行失败,对应的测试用例就会被禁用(不管是否重新运行),每个开发团队都需要分配资源来调查测试失败的真实性质,以进行相应的修复。
第二种方案:停用不稳定的测试用例
我们把对测试失败的处理分为3个关键部分。
- 测试检测。识别测试失败,并区分不稳定的测试和失败的测试。
- 测试隔离。创建一个Jira问题单,提交一个PR来停用测试(译者注:相当于提交了测试代码变更的Pull Request),系统会自动审批通过这个PR,然后测试代码变更被合入。
- Slack通知。当这个PR被创建和合并时,系统会自动通知DevXp团队。
1. 成功指标
将主分支任务的通过率提高到95%,将测试任务的失败率降低到5%以下。
2. 具体需求
-
- 主分支上的所有测试任务都必须通过,因为事先在PR分支上是通过的
- 支持所有的测试类型:E2E、功能和单元测试
- 可以灵活地自动检测并分别隔离失败的测试用例和不稳定的测试用例
- 对后端/API故障、测试崩溃或基础设施故障引起的失败不采取隔离措施。
- 支持两种移动系统:iOS和Android
- 给测试用例的所有权创建映射,将测试用例自动关联到所负责的开发团队
- 如果测试用例是不稳定的,就给负责这条测试用例的开发团队分配一个Jira问题单
- 将测试结果的失败细节附到Jira问题单中,以方便调查
- 排除一些特定的测试,让团队可以选择是否停用它们
- 灵活地选择是自动还是手动合并一个受到测试隔离影响的PR
- 每周向每个团队通报被停用的测试用例的情况。
方案二:停用不稳定的测试用例
3. 具体实施方案
检测不稳定的测试用例的代码如下所示:
停用不稳定的测试用例、创建Jira和PR的代码如下所示:
根据操作系统类型停用测试用例的代码如下所示:
4. 实施效果
方案二实施后,让我们看看我们在指标上的表现:
主干分支的稳定性提高到96%。从2020年7月27日的19.82%到2021年2月22日的96%。对稳定性有负面影响的领域主要是是第三方服务和合并冲突。
测试任务的失败率降低到3.85%。从2020年7月27日的56.76%到2021年2月22日的3.85%。受到负面影响的领域是第三方服务、Infra停机时间和CI。
节省了553小时的人工排查时间。人工排查不稳定的测试需要~28分钟/PR。通过自动化,我们为安卓系统创建了693个PR,为iOS系统创建了492个PR,到目前为止,总共节省了553个小时(或23天的开发人员时间)。
开发人员的情绪和体验得到了改善。有一位开发人员这样说:"我觉得iOS CI比以前更加稳定和快速。感谢你们的辛勤工作! 它极大地提高了我们的工作效率。真的很感激!"
另外,开发人员的信心也得到了很大提高。我们联系了开发人员进行调查,从调查结果中可以看出,信心已经朝着积极的方向增加。
- 74%的人说#proj-cornflake对主干分支的稳定性有积极影响
- 64%的人说#proj-cornflake减少了PR上的重新运行。
自该方案推出以来已近一年,自动检测和隔离系统仍在持续运行,几乎没有维护成本。
还有更多的故事吗?
当然有。在开发者用户体验访谈中,我们提出了以下问题:
(当我收到# project-cornflake所生成的、被隔离的不稳定测试的问题时,很容易就能旗开得胜吗?)
总结: 26%的人的答案是否定的,58%的人持中立态度。
在推出项目几个月之后,许多测试都被隔离了,开发人员在重新启用测试时遇到了一些困难,因为测试失败可能已经过时,很难重现,或者功能特性已经更新了。
在未来,我们将这个平台改造成更加智能的系统,并增加了对自动启用测试的支持,同时尝试满足以下来自开发人员反馈的新需求:
- 实时向各自的特性团队发送Flaky测试的通知;
- 在隔离中定期重新运行这些不稳定的测试,以确保它不会影响主分支的构建。一旦它们稳定运行,那么它们可以自动重新启用并合并到主分支;
- 使修复不稳定测试的过程尽可能简单和快速。
最后: 下方这份完整的软件测试视频学习教程已经整理上传完成,朋友们如果需要可以自行免费领取【保证100%免费】
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!