SEO 摘要: 本文介绍如何在大规模移动端研发场景中治理不稳定测试,重点讨论持续集成、主分支稳定性、测试失败率、自动化测试、测试检测、测试抑制、测试归属、工单与拉取请求自动化、隔离环境重跑和自动重新启用等实践,帮助团队降低测试作业失败率,提升开发者体验和交付效率。

某协作平台移动端开发者体验团队的目标,是帮助开发人员更有信心地交付代码,同时获得更愉悦、更高效的工程体验。团队会通过多类指标和调研来衡量生产力与开发体验,例如开发者满意度、持续集成稳定性、合并时间和测试失败率。
开发者体验团队持续投入资源,改进测试基础设施和持续集成流程。但阻碍进展的最大问题之一,是测试失败率长期居高不下,而且还在持续上升。深入分析后,团队发现,这很大程度上是由测试本身不稳定造成的。
对于企业研发团队而言,不稳定测试治理并不只是测试平台问题,也会影响需求评审、开发实现、测试验证、缺陷追踪、发布上线和经验沉淀等完整研发链路。借助 PingCode 这类智能化研发管理工具,团队可以将目标、需求、开发、测试、发布、缺陷和 Wiki 知识沉淀串联起来,并打通研发工具链,让测试稳定性治理过程更加自动化、数据化和可追踪。
在实现不稳定测试自动处理之前,主分支稳定性检查的通过率一度徘徊在 20% 左右。团队进一步分析了另外 80% 的失败构建,发现其中:
- 57% 的失败来自测试作业失败,包括不稳定测试和真实失败的自动化测试;
- 13% 的测试失败来自开发人员错误、持续集成问题或测试基础设施问题;
- 10% 的合并失败来自合并冲突。
在用自动化不稳定测试抑制系统取代手动处理测试失败的方法后,团队成功将测试作业失败率从 57% 降至 5% 以下。
本文将介绍团队如何通过自动化测试失败检测和抑制方法,最大限度减少不稳定测试带来的影响。这并不是一个全新的问题,许多大型工程组织都曾构建过类似系统来处理不稳定测试。本文的重点,是说明测试不稳定性如何在规模扩大后变得越来越严重,以及某协作平台如何有效控制这一问题。
不稳定测试治理背景:从更宏观的角度看
在移动端代码库中,团队有 120 多位开发人员,每周提交 550 多个拉取请求。移动端 A 平台有 16,000 多个自动化测试,移动端 B 平台有 11,000 多个自动化测试。测试体系由端到端测试、功能测试和单元测试共同组成。
所有测试都会在两类场景中运行:一是每次提交到拉取请求时,二是每次拉取请求合并到主分支时。此外,开发人员负责为自己所在产品组相关功能编写和维护自动化测试。
随着规模不断扩大,团队开始思考一个问题:如何确保把变更提交到主分支的体验既可靠又高效?
一开始,团队投入大量资源开发更可靠的测试。然而,当规模扩大后,仅靠测试编写者确保测试正确性,已经无法完全避免系统中的不稳定因素。
早期,团队依赖一种“集体意识”:开发人员发现某个测试不稳定后,会主动进行调查。这种方法在小团队中很有效,例如 10 人左右的开发团队。但当组织规模扩大后,就会出现“旁观者效应”。最终,开发人员通常只想合并自己正在处理的拉取请求,而不是调查一个看起来与自己无关的不稳定测试。
根据对开发人员和问题分诊人员的调研,每个测试失败的手动分诊平均需要 28 分钟。系统中不可避免的不稳定因素、较高的测试作业失败率,以及耗时的人工分诊流程,促使团队开始投入不稳定测试处理自动化。
先聚焦不稳定测试
可以先用软件中的致命错误和非致命错误来类比。
任何软件都难免遇到致命错误和非致命错误。致命错误会影响软件的基本使用,造成严重用户体验问题。非致命错误同样会影响使用体验,但影响程度较轻。开发人员可能不会立即处理非致命错误,但持续监控这些错误仍然非常重要。
对于规模足够大的测试集来说,不稳定测试结果也是不可避免的。不稳定测试结果会破坏工作流和质量之间脆弱的平衡。
缺陷可能隐藏在不稳定测试结果之下。如果开发人员仅仅因为测试结果“不稳定”而忽略它,那么这个测试就失去了存在意义。不稳定测试在运行时还会占用宝贵资源,并增加执行成本。尤其是在持续集成依赖第三方云服务或移动设备测试服务时,这类成本会更加明显。
最终,不稳定测试会降低持续集成稳定性,延长产品发布时间,削弱开发人员对测试系统的信任,并直接影响开发体验。
团队将不稳定测试分为两类:
- 独立不稳定测试:无论单独运行,还是作为测试集的一部分运行,都会失败。这类测试相对更容易识别、调试和修复,因为单独运行它们就能复现失败。
- 系统性不稳定测试:这类测试通常由于共享状态、持续集成环境差异或运行顺序变化而失败。它们只有在作为测试集的一部分运行时才会暴露问题。随着测试集或持续集成环境变化,测试行为也会变化,因此更难检测和调试。
在初步调研中,一些开发人员反馈:他们认可持续集成中运行多种测试的价值,但也希望端到端测试和移动设备测试的不稳定性能够进一步降低。很多需要设备参与的持续集成错误,最终都来自不稳定测试。
团队最初的实现,主要是为了解决系统性问题导致的测试不稳定。
人工分诊不稳定测试的成本
在自动化系统出现之前,当开发人员的拉取请求或主分支上的测试作业失败时,通常需要遵循一套手动流程。
首先,开发人员收到通知:由于某个作业失败,一项必需检查被标记为失败。
随后,开发人员开始调查,并发现这个作业失败是由自动化测试失败引起的。
接着,开发人员判断失败测试是否与当前拉取请求中的代码变更有关。如果认为无关,他们通常会尝试重新运行失败的测试作业,希望第二次能够通过。有时,这个步骤会被重复多次。由于测试不稳定,这些测试可能会在某次重跑后“神奇地”通过。
如果重跑仍然失败,开发人员会联系开发者体验团队的帮助渠道。值班人员会进一步调试失败的测试作业,通常包括:
- 检查其他拉取请求或主分支测试作业是否也因为同一个测试失败;
- 确认开发人员已经重试了多少次,以及测试是否曾在某个时间点开始通过;
- 查看测试历史记录,确认测试最近是否被修改过;
- 检查该测试是否存在不稳定倾向;
- 尝试找出负责该测试的团队;
- 抑制该测试,让开发人员可以继续开发流程,并解除拉取请求合并阻塞;
- 创建一个包含所有调查细节的工单,并分配给负责团队。
数据显示,手动分诊并抑制不稳定测试,确实可以提高系统稳定性,并减少测试作业失败率。因此,团队决定将这一过程自动化。
不稳定测试自动化治理:从哪里开始
成功并非一蹴而就。团队经历了多次尝试,才逐步找到更稳妥的方案。
团队启动了一个内部项目,目标是自动检测和抑制不合格测试。首先,需要明确区分“不稳定测试”和“失败测试”:
- 失败测试:多次重复运行均失败的测试。
- 不稳定测试:多次重复运行后,最终能够通过的测试。
第一种方法:达到阈值后抑制测试结果
第一种思路是创建一个系统,自动检测不稳定测试,并根据测试历史记录计算不稳定比例。如果某个测试的不稳定程度超过可接受阈值,就将其从结果中移除,避免测试失败导致整个测试作业失败。
不过,不同测试类型的失败率并不相同。通常,单元测试的失败率低于功能测试,功能测试的失败率又低于端到端测试。基于这一点,团队决定按照测试类型,以及每种测试类型对应的失败容忍度来制定策略。
第一种方法的执行方式
对于每次测试作业失败,持续集成系统会执行以下流程:
- 解析测试结果,识别失败的测试用例;
- 对每个失败测试用例,从后端系统中拉取主分支最近 N 次测试运行历史,例如 N = 50;
- 计算每个失败测试的不稳定比例:失败次数 / 测试运行次数;
- 如果某个测试没有历史记录,或历史记录不足,则自动将其视为不稳定测试,也就是默认假设它不稳定,直到它证明自己稳定;
- 从测试结果中移除超过特定阈值的不稳定测试结果;
- 更新后端系统,将对应测试标记为不稳定;
- 持续集成系统接收修改后的测试结果文件,并将测试作业标记为通过。
第一种方法的效果
项目上线后,团队立即观察到拉取请求分支和主分支测试作业稳定性提升。
拉取请求分支的构建稳定性从上线时的 71% 提升到下一周的 88%。主分支构建稳定性也在下一周从 61% 提升到 90%。
从短期看,这种方法效果明显。
第一种方法的缺点
虽然这种方法在初期推广阶段效果不错,但团队很快发现了越来越多问题。这些问题让作业失败原因的调查变得更加困难。最终,导致团队回滚实现的最大问题是:失败测试代码可能泄露到主分支。
缺点一:不稳定测试变成失败测试,并泄露到主分支
可以看一个典型场景:
开发人员 A 编写了一个全新的有效测试,并将其合并到主分支。
然而,由于后端系统中缺乏足够的测试历史记录,系统会将这个测试归类为不稳定测试,并从测试结果中移除,同时把测试作业标记为成功。
此时,主分支上的测试实际上已经失败,但由于缺乏足够历史记录,它在持续集成中仍然被过滤掉。与此同时,该测试在本地运行时也会失败,给开发人员造成困扰。
一旦该测试积累了足够历史记录,并超过容错阈值,测试作业就会开始在持续集成中失败。此时,开发人员或问题分诊人员就不得不回头调查测试从什么时候开始失败,这违背了项目的初衷。
缺点二:重新启用不稳定测试时容易误判
再看另一个场景:
开发人员 A 提交了一个拉取请求,试图修复一个不稳定测试。但实际修复无效,该测试仍然失败。
这种情况下,持续集成作业应该失败,并且不应该从结果中删除该测试。
但系统会查看测试历史记录,找到足够历史数据来计算不稳定比例,然后继续将该测试从结果中删除,并把它归类为不稳定测试,从而让作业通过。
开发人员看到作业状态通过,可能会认为测试已经修复,于是关闭对应工单。但实际上,这个测试仍然不稳定。
缺点三:过度依赖后端系统
本地开发体验也会受到影响。测试历史记录的权威来源位于后端系统,这意味着开发人员在本地运行测试时,会遇到与持续集成不同的体验。要把这套系统同步到本地开发环境并不容易。
此外,如果后端系统宕机,持续集成测试任务会立即停止。测试任务可能失败,并显示“无法执行不稳定测试计算”之类的错误信息。这会阻止开发人员合并拉取请求,也可能导致主分支作业无法更新后端。
第一种方法的主要经验
团队从第一种方法中学到了几点重要经验。
首先,简单过滤掉不稳定测试结果并不是最佳方案,因为这种做法只是掩盖了测试失败对整体构建稳定性的影响。
其次,这种方法会让不稳定测试调查变得更困难,因为团队缺乏足够信息来确定测试状态从什么时候发生变化。
最后,与其同时在拉取请求和主分支层面处理测试失败检测与抑制,不如只在主分支层面抑制测试。这样,开发人员只要拉取最新主分支,就可以避免在自己的拉取请求中遇到已知的不稳定测试失败。
因此,团队决定改变方向:不再抑制不稳定测试的结果,而是抑制不稳定测试的执行。
一旦测试失败,无论重新运行后是否通过,该测试都会被禁用。随后,每个功能团队都需要分配资源,调查测试失败的真正原因,并进行相应修复。
第二种方法:抑制不稳定测试的执行
第二种方案将测试失败处理拆分为三个主要部分:
- 测试检测:识别测试失败,并区分不稳定测试和真实失败测试。
- 测试抑制:创建工单,提交一个用于禁用该测试的拉取请求,自动批准该拉取请求,并将其合并。
- 团队通知:当拉取请求创建和合并时,通知开发者体验团队及相关负责人。
在这样的自动化流程中,测试抑制并不只是代码层面的变更,还涉及任务分派、负责人确认、工单跟进、通知同步和后续复盘。团队可以借助 Worktile 这类通用项目协作系统统一管理任务、项目、文档、IM、目标、日历、甘特图、工时和审批流程,让不稳定测试治理中的跨团队协作更加清晰可控。
成功指标
团队为第二种方法设定了清晰目标:
- 将主分支作业通过率提高到 95%;
- 将测试作业失败率降低到 5% 以下。
方案要求
新的系统需要满足以下要求:
- 主分支上的所有测试运行都必须通过,因为这些代码已经经过拉取请求检查;
- 支持所有测试类型,包括端到端测试、功能测试和单元测试;
- 能够灵活地自动检测并分别抑制失败测试和不稳定测试;
- 不应抑制后端或 API 故障、测试崩溃或基础设施故障;
- 支持两个移动端平台的测试抑制;
- 创建测试归属映射,将测试映射到对应功能团队;
- 将工单分配给负责该测试的团队;
- 将测试失败详情附加到工单中,方便后续调查;
- 如果某个团队选择不自动禁用特定测试,系统应支持排除这些测试;
- 支持自动或手动合并因测试抑制而产生的拉取请求;
- 每周向各团队频道发送提醒,汇总已被抑制的测试。
第二种方法的执行方式
检测测试失败
系统首先从原始测试结果中获取不稳定或失败测试列表,同时排除后端/API 故障、测试崩溃和基础设施故障,因为这些问题通常与测试逻辑无关。
简化伪代码如下:def get_test_failures_from_raw_results(): test_failures = [] result_files = get_list_of_test_result_files_from_ci() for result_file in result_files: for test in result_file: if test.status in [“failure”, “flakyFailure”] and not test.is_infra_incident and not test.is_crash and not test.is_api_failure: test_failures.append(test) return test_failures
禁用测试、创建工单和拉取请求
系统随后会为每个失败测试执行以下操作:
- 查找测试负责人和对应工单项目;
- 如果工单不存在,则创建工单;
- 创建并切换到一个新分支;
- 禁用对应测试;
- 提交并推送变更;
- 创建拉取请求,分配评审人;
- 自动批准并合并该拉取请求。
简化伪代码如下:def disable_test_with_ticket_and_pr_creation(test_failures): for test_name in test_failures: owner_team, ticket_project_id = get_team_owner_and_ticket_project() ticket = find_or_create_ticket(ticket_project_id) branch_name = create_and_checkout_git_branch() disable_test(test_name, ticket) commit_and_push_changes(branch_name) pr = open_pr_and_assign_reviewer(ticket, branch_name) approve_pr_and_merge(pr)
按平台禁用测试
系统会根据不同移动端平台修改测试文件,禁用对应测试。
简化伪代码如下:def disable_test(test_name, ticket): test_file_path = get_file_path_for_test(test_name) with open_in_place(test_file_path) as test_file: for line in test_file: test_found = search_test_name(test_name, line) if test_found: if platform == “mobile_platform_a”: disable_platform_a_test(ticket) elif platform == “mobile_platform_b”: disable_platform_b_test(ticket) test_file.write(line)
对于移动端 A 平台,系统通过重命名测试函数的方式禁用测试,并在注释中添加工单链接。例如:// https://ticket-system.example/PROJ-123 func disabled_testShouldShowInvite() {
对于移动端 B 平台,系统通过添加忽略注解的方式禁用测试,并在注解中添加工单链接。例如:@Ignore(“https://ticket-system.example/PROJ-123”) fun testShouldShowInvite() {
这种方式的优势是:被禁用测试不会继续影响主分支稳定性,同时也不会悄悄从结果中消失。它会留下明确的工单、代码变更和责任归属。
第二种方法的效果
实施完成后,团队重新评估了成功指标。
主分支稳定性提升到 96%
主分支稳定性从 2020 年 7 月 27 日的 19.82%,提升到 2021 年 2 月 22 日的 96%。
需要注意的是,影响稳定性的因素仍然包括第三方服务问题和合并冲突,但不稳定测试对主分支的影响已经显著降低。
测试作业失败率降至 3.85%
测试作业失败率从 2020 年 7 月 27 日的 56.76%,下降到 2021 年 2 月 22 日的 3.85%。
受影响较大的领域包括第三方服务、基础设施停机和持续集成问题。即便如此,测试本身不稳定带来的主要负担已经大幅降低。
节省 553 小时故障排查时间
人工排查一个不稳定测试用例,平均大约需要 28 分钟。通过自动化系统,团队已经为移动端 A 平台创建了 693 个拉取请求,为移动端 B 平台创建了 492 个拉取请求。
截至统计时,系统总共节省了约 553 小时,也就是 23 天左右的开发人员时间。
开发者满意度提升
有开发人员反馈:移动端持续集成比以前稳定得多,也更快了。这项工作显著提升了团队生产力。
开发者信心增强
团队也主动联系开发人员收集反馈,调查结果显示,开发人员对持续集成系统的信心明显朝积极方向变化。
其中:
- 74% 的受访者认为该项目对主分支稳定性有积极影响;
- 64% 的受访者表示该项目减少了拉取请求中重跑测试的次数。
第二版系统上线近一年后,自动检测和抑制系统依然运行良好,并且几乎不需要额外维护成本。
自动重新启用测试:故事还有后续吗?
当然有。
在开发者体验访谈中,团队继续收集反馈。总结来看,26% 的开发人员认为重新启用被抑制测试并不容易,58% 的开发人员对这一体验持中立态度。
项目推出几个月后,许多测试已经被抑制。开发人员发现,重新启用测试并不简单,因为测试失败可能已经过时、难以复现,或者相关功能已经发生变化。
在项目下一阶段,团队对系统进行了进一步优化,使其更加智能,并增加了自动重新启用测试的能力。同时,团队努力满足开发者反馈中的几个要求:
- 实时向相应功能团队发送测试抑制通知;
- 定期在隔离环境中重新运行被抑制测试,确保它们不会影响主分支构建;
- 如果测试不再不稳定,系统可以自动重新启用这些测试,并将变更合并到主分支;
- 尽可能简化并加快修复被抑制测试的过程。
结论:自动化治理不稳定测试,提升持续集成稳定性
在大规模移动端研发场景中,不稳定测试并不是偶发噪声,而是会持续影响持续集成稳定性、开发者信心、合并效率和工程成本的系统性问题。
最初,团队尝试通过抑制测试结果来提升构建稳定性。这种方法在短期内有效,但也带来了明显问题:它会掩盖真实失败,让失败测试泄露到主分支,并增加后续调查难度。
随后,团队转向抑制测试执行。通过自动检测失败测试、创建工单、提交禁用测试的拉取请求、自动合并变更,并将测试归属映射到负责团队,系统在保证透明度和责任归属的同时,大幅提升了主分支稳定性。
最终,主分支稳定性从约 20% 提升到 96%,测试作业失败率从约 57% 降至 4% 以下,同时节省了数百小时人工分诊时间。
这项实践说明:治理不稳定测试不能只靠开发人员的自觉,也不能只靠人工分诊。随着团队规模、测试规模和持续集成复杂度增长,测试稳定性治理必须自动化、可追踪,并与清晰的责任归属机制结合起来。只有这样,团队才能在保持高质量交付的同时,持续改善开发者体验。
文章包含AI辅助创作:不稳定测试治理:如何通过自动检测与自动抑制提升 CI 稳定性,发布者:shang,转载请注明出处:https://worktile.com/kb/p/3979046
微信扫一扫
支付宝扫一扫