摘要:不稳定的测试在软件开发中是一个重大挑战,会导致挫败感和效率低下。这些自动化测试在代码库未发生变化的情况下表现出不一致的通过/失败行为,极大地浪费了资源,并损害了测试流程的可靠性。
摘要:
不稳定的测试在软件开发中是一个重大挑战,会导致挫败感和效率低下。这些自动化测试在代码库未发生变化的情况下表现出不一致的通过/失败行为,极大地浪费了资源,并损害了测试流程的可靠性。
本文探讨了不稳定的测试所带来的常常被忽视的成本,分析了其潜在原因,并讨论了有效的缓解策略。
作为一名拥有十多年测试自动化经验的软件工程师,我亲眼目睹了不稳定的测试所带来的挫败感和低效率。
这些反复无常的测试在代码库没有任何改动的情况下时而通过时而失败,它们不仅仅是令人讨厌的小麻烦,更是对资源的巨大消耗,也是对我们测试流程完整性的威胁。
在本文中,我们将探讨不稳定的测试对软件开发的真正影响,剖析其根源,并讨论减轻其影响的策略。
读完本文,您将明白为何解决测试不稳定性问题应当成为任何追求高效和可靠的开发团队的优先事项。
不稳定的测试的真正代价
偶尔出现的测试失败可能看起来只是个小麻烦。然而,不稳定的测试所带来的累积影响可能会令人震惊:
01、浪费的开发人员时间
谷歌的一项研究表明,不稳定的测试导致的测试失败占 4.56%,耗费了公司超过 2%的编码时间。
对于一个由 50 名开发人员组成的团队来说,这意味着每年要浪费整整一个人年的工时来处理不可靠的测试。
为了更清晰地理解,让我们来剖析一下这些数据:
但实际成本远不止于此。还要考虑由于这些时间的损失而错失的功能开发、未修复的漏洞以及未进行的改进所带来的机会成本。
02、延迟发布
在持续集成/持续部署(CI/CD)环境中,不稳定的测试可能会引发错误警报,从而中断流水线。
GitLab 的一项调查发现,36% 的开发人员每月至少会因测试失败而经历一次延迟发布。
让我们来审视一下潜在的影响:
这还不包括紧急会议的成本、额外的质量保证工作以及可能失去客户信任所带来的损失
03、信任的侵蚀
当测试变得不可靠时,开发人员就会开始忽视测试结果。这会破坏自动化测试的全部意义,并可能导致真正的漏洞被忽视而未被发现。
信任的丧失可能会以多种方式表现出来:
来自 Mozilla 的一项案例研究发现,在解决了不可靠的测试问题之后,开发人员对测试套件的信心提高了 29%,从而加快了问题解决速度,减少了漏掉的漏洞。
04、认知负荷增加
调试不稳定的测试通常需要频繁切换上下文并进行深入调查,这会打断开发者的思路和工作节奏,降低工作效率。
考虑以下场景:
这种上下文切换不仅浪费时间,还会破坏解决复杂问题所需的深度专注力,可能影响开发人员原本正在处理的主要任务的质量。
05、测试不稳定的根源
要有效应对不稳定的测试,我们需要了解其根源。常见原因包括:
异步等待问题:未能正确处理异步操作的测试可能会因时间差异而间歇性失败。这种情况在前端测试以及与外部服务交互时尤为常见。
示例场景:
常见的罪魁祸首包括:
未关闭的数据库连接未删除的临时文件。全局状态修改不重置。外部依赖:对外部服务或数据库的依赖可能导致不一致。这些依赖关系可能有它们自己的可靠性问题,或者可能受到速率限制,从而导致零星的测试失败。并发性问题:多线程应用程序中的竞争条件可能导致零星的故障。这些通常是最难诊断和修复的测试。环境不一致:开发、测试和生产环境之间的差异可能导致不稳定。这可能是由于不同的操作系统版本、库版本或配置设置。对抗不稳定测试的策略
既然我们了解了不稳定测试的影响和原因,让我们探讨解决这些问题的策略:
01、实施适当的等待机制
用智能等待取代任意的sleep语句。
像WebDriverWait for Selenium或AsyncTest for JavaScript这样的库可以帮助更可靠地管理异步操作。
# 把这个替换掉:time.sleep(5)element.click# 使用以下语句:WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "myElement")))element.click此外,考虑为复杂场景实现自定义等待条件:
def wait_for_data_load(driver):return len(driver.find_elements_by_class_name("data-row")) > 0WebDriverWait(driver, 10).until(wait_for_data_load)02、独立测试
确保每个测试都独立运行,并具有自己的安装和拆除过程。这可以防止测试相互干扰,并且更容易找到脆弱的来源。
def setUp(self):self.db = create_test_databaseself.app = create_test_app(self.db)def tearDown(self):self.db.clearself.app.shutdown考虑使用容器化为每个测试提供一个新的环境:
# 为测试独立编写文件version: '3'services:test:build: .command: python -m unittest discover testsenvironment:- DATABASE_URL=postgres://testuser:testpass@db:5432/testdbdb:image: postgres:13environment:- POSTGRES_USER=testuser- POSTGRES_PASSWORD=testpass- POSTGRES_DB=testdb03、模拟外部依赖
使用mock框架来模拟外部服务和数据库。这减少了对潜在的不稳定外部因素的依赖。
@patch('myapp.external_service.api_call')def test_feature(mock_api_call):mock_api_call.return_value = {'status': 'success'}result = my_featureassert result == 'expected output'对于更复杂的场景,可以考虑使用像WireMock或MockServer这样的工具来模拟整个API端点。
04、实现重试机制
对于由于无法控制的因素而天生容易出现偶尔失败的测试,请实现重试机制。
但是,要谨慎使用这种方法,并将其作为最后的手段,因为它可能掩盖潜在的问题。
@retry(stop_max_attempt_number=3, wait_fixed=1000)def test_with_retry:# 这里是测试逻辑pass05、持续监测和分析
实现跟踪测试稳定性的工具。许多CI/CD平台提供内置的不稳定测试检测。
例如,Jenkins有“测试稳定性历史”插件,它提供了对测试可靠性的见解。
要超越内置工具,请考虑实现自定义不稳定测试检测系统:
1.将测试结果存储在数据库中,包括元数据,如执行时间、环境细节和失败的堆栈跟踪。
2.实现在测试失败中检测模式的算法。
3.生成测试稳定性和趋势的定期报告。
4.自动隔离超过脆弱阈值的测试,以供进一步调查。
06、优先修复不稳定的测试
将不稳定的测试视为技术债务。在每个sprint中分配专门的时间来调查和修复不可靠的测试。
谷歌的工程生产力研究团队发现,尽早解决不稳定的测试可以显著降低它们的长期影响。
为你的团队执行一个“不稳定的测试预算”:
1.为不稳定测试的最大可接受数量设置目标(例如,
2.定期检查和优先处理不稳定的测试。
3.为零散的测试修复分配故事点或时间估计,将它们视为一流的开发任务。
4.考虑为长期存在的不稳定测试实现“修复或删除”策略。
案例研究:
微软与不可靠测试的斗争
微软处理不可靠测试的历程提供了宝贵的见解。
在2020年国际软件工程会议的演讲中,微软的研究人员分享了他们的经验:
1.他们开发了一种名为“Deflaker”的工具,用于自动识别和隔离不可靠测试。
2.通过在全公司范围内实施一项政策,在两周内修复或删除不可靠的测试,他们在六个月内将整体测试不可靠率降低了18%。
3.该计划使开发人员的工作效率提高了2.5%,相当于节省了数百万美元的工程时间。
4.微软为每个项目实施了一个“不稳定的测试分数”,这成为他们工程健康指标的一部分。
5.公司开发了编写稳定测试的最佳实践和培训材料,这成为新开发人员入职过程的一部分。
结论
不可靠的测试不仅仅是一个小麻烦——它们是对资源的重大消耗,并威胁到我们软件开发过程的可靠性。
通过理解它们的原因并实施有针对性的策略来解决它们,我们可以显著地提高我们测试工作的效率和有效性。
记住,我们的目标不仅仅是让测试通过,而是让我们可以信任测试。
将时间和资源投入到对抗测试缺陷中,可以获得更快的发布速度、更高的开发人员生产力和更高质量的软件。
作为一个行业,我们必须以与应用于特性开发相同的精力来优先考虑测试可靠性。只有这样,我们才能充分认识到自动化测试和持续集成的好处。
将您的测试可靠性工作提升到一个新的水平:
1.对您当前的测试套件进行一次零散的测试审核。
2.实施不稳定测试检测和监控系统。
3.建立明确的政策和程序来处理不稳定的测试。
4.投资开发人员编写稳定测试的培训。
5.根据收集到的数据,定期审查和改进您的测试策略。
通过使测试可靠性成为开发文化的核心部分,您不仅可以改进测试过程,还可以提高整体软件质量和团队生产力。
来源:家庭健康好生活