持续集成之道 - The Trail of Bits博客
作者:Paul Kehrer | 2021年2月26日
分类:教育、指南
持续集成需要明确目标
现代软件开发中有一个不言自明的真理:健全的持续集成(CI)系统必不可少。但许多项目却饱受CI系统脆弱、挫败开发者并直接阻碍开发速度的困扰。为什么会这样?如何避免常见的CI陷阱?
CI本应为项目代码的正确性提供额外保障。然而,开发者编写的用于验证预期功能的测试,在最初编写时恰恰是最无用的。这或许有违直觉,因为开发者最初编写代码时熟悉度最高——他们从多个角度思考过代码,考虑了许多可能的边缘情况,并实现了运行良好的功能!
遗憾的是,编写代码只是编程中最简单的部分。真正的挑战在于构建他人可读的代码,使项目能够长期繁荣发展。软件熵随时间推移而增加。开发者——尤其是不熟悉大型长期代码库的开发者——无法预料他们的代码将如何被集成、重构和重新调整用途,以满足最初未考虑的需求。
当这类重构和扩展发生时,测试是能够自信进行更改的唯一方法。那么为什么开发者最终会缺乏高质量测试的系统?
无意义的测试
在编写测试时,特别是为了高代码覆盖率指标时,最常见的抱怨是某些测试毫无意义,没有触及代码库中任何有趣或容易出错的部分。如果只考虑当前存在的代码,这些抱怨是合理的,但现在设想软件可能被重新调整用途。曾经无关紧要的部分现在可能变得微妙。未能测试琐碎情况可能导致你的工作陷入源于不可观察行为的隐藏陷阱迷宫。
请记住这三件事:
- 从长远来看,没有测试是无关紧要的
- 测试是预期行为的文档
- 未经测试的代码容易发生意外行为变更
不可靠的CI
不可靠的CI对开发者来说是毒药。对于内部项目,它会降低生产力并让人讨厌在其上工作。对于开源项目,它驱赶贡献者的速度比他们到来的速度更快。
找出导致测试不可靠的原因并修复它。不可靠的CI通常表现为不稳定的测试,现有工具可以将测试标记为不稳定,直到你找到根本原因。这将使你的CI立即改进,而不会拖累团队。
缓慢的CI
你可能会遇到过长的CI周期时间。这是有问题的,因为高质量的开发流程要求所有CI作业都通过。如果周期时间太长太复杂,以至于在本地运行不切实际,那么开发者将创建变通方案。这些变通方案可能采取多种形式,但最常见的是PR规模膨胀——当没有人愿意提交一个2行的PR,等待一个小时让它合并,然后重新调整他们300行的PR时。更何况他们可以在单个PR中做一些不相关的更改。这给代码审查者带来问题并降低项目质量。
开发者这样做并没有错,是CI让他们失望了。在构建CI系统时,必须牢记延迟预算:“CI绝不应慢于时间t,其中t是先验选择的。“如果CI变得比这更慢,那么就需要努力改进它,即使这会侵占新功能的开发。
覆盖率难题
负责任测试的一部分是了解测试正在执行哪些代码行——这是一个简单明了的数字,告诉你一切。那么为什么覆盖率如此常被忽视?
首先是技术挑战。现代软件在许多不同的目标上运行。要有用,CI系统应该在多个目标上运行,这些目标将数据提交到可以合并覆盖率的托管系统。(这类工具失败带来的挫败感,以及如何在所有软件都是热垃圾的情况下保持开发速度是另一个话题。)如果没有这个,服务软件开发人员通常不会注意到遗漏的覆盖率,因为它消失在"预期"遗漏行的噪音中。
现在让我们谈谈社会挑战。软件通常以难以测试小功能块的方式编写。这个问题催生了测试驱动开发(TDD)趋势,即先编写测试以帮助开发者以可测试的方式分解代码。这通常在可读性和可测试性方面是净收益,但需要纪律和不同的开发方法,这对许多人来说并不自然。
使更多代码库可测试的 perceived drudgery 导致抱怨覆盖率是一个不完美的指标。毕竟,并非所有代码分支都是平等的,根据你的语言,某些代码路径永远不应被执行。这些都不是否定覆盖率作为有价值指标的好理由,但在特定情况下,可能存在令人信服的理由不花费精力用测试覆盖某些内容。但是,请注意,未能用测试覆盖某段代码,其行为就不再是未来开发者在重构期间将遵守的契约的一部分。
我们该怎么做?
那么,面对所有这些障碍,我们如何达到CI的涅槃境界?逐步进行。现有项目是宝贵资产,我们希望保留现有成果,同时提高未来改进的能力。(重写几乎总是一个坏主意。)这需要一种渐进的方法,虽然专门针对给定项目定制,但有一个广泛的配方:
- 使CI可靠
- 加速CI
- 提高测试质量
- 改进覆盖率
我们都应该花时间投资于项目的长期性。这类基础性努力会迅速带来回报,并确保你的软件项目能够达到世界级水平。