持续集成(Continuous Integration, CI)是软件工程的一个重要领域。在现代软件开发中,多个开发者(Developer)工作在同一个共享的代码仓库(Repository)上。每一次开发者往代码仓库主干(Master/Trunk)提交(Submit)或者合入(Merge)代码改动的过程,可以认为是一次集成。
持续集成,就是持续的代码合入过程。每一次集成,绝非是将开发者的代码改动与主干已有代码合并这么简单,而是一个复杂的,通常包括代码合并,构建,静态检查,动态检查,运行,多级测试(单元测试/模块测试/集成测试),代码评审等一系列任务的系统工程。这些任务的侧重点各有不同,但是其目标是共同的,那就是检验开发者的代码改动是否符合质量要求,确保主干不被代码改动破坏。
持续集成的内容固然可以包罗万象,然而其核心要义仍然在于持续二字。在进一步了解持续集成之前,我们先看看持续集成的对立面是什么。
一次性集成:将开发和集成分离,等开发工作全部完成之后,再进行集成。这种方案会遇到集成黑洞(Integration Hell)问题:在集成阶段,大量的软件问题集中爆发,层出不穷。由于集成的对象包含了非常多的代码改动,并且代码改动的时间已经过去较长时间,软件问题的定位和解决将困难重重。此时的集成,将吞噬有限的项目时间,阻止项目往前推进,成为项目交付的重大瓶颈。
非持续集成:集成的频率介于一次性集成和持续集成之间,表现形式有多种。例如,按照时间周期性地集成,每周集成一次;或者按照特性(Feature)集成,每完成一个新特性的开发就集成一次。这种方式能够避免产生大的集成黑洞问题,但是每一次集成仍然可能会遇到较小的集成黑洞。对于发现的软件问题,定位和解决难度仍然不小。
解决集成黑洞的办法是做到真正的持续集成。那么,什么是真正的持续集成呢?这要回答一个最原始的问题,那就是为什么要集成?集成的对象是什么?持续集成的目的,是验证代码改动是否能够合入主干。集成的对象应当是开发者每一次的代码改动。代码改动是事件(Event)。持续集成应该是事件触发,而不是时间触发。
基于这个初心,凡是有代码改动的时候,就应该进行集成。从形态来说,这里的改动包含新代码增加,已有代码修改和已有代码的删除。从目的来说,这里的改动包含新特性的实现,软件问题的修复,已有代码的重构等。“差之毫厘,谬以千里”。任何一行代码改动都需要经过充分验证才能被接受。没有经过验证和集成的代码改动,不能合入主干。主干的质量至关重要。由于每次集成均依赖于主干,一旦主干被破坏,所有其他开发者的代码改动将都无法正常提交,造成严重后果。
我们可以认为,由代码提交(Commit)所触发的集成,才是真正的持续集成。或者说,一次提交,一次集成(One Commit,One Integration)。这样的持续集成,其好处是多方面的。
及早发现和解决软件问题:一旦代码改动有问题,便可以通过集成来发现,并且原地和及时地反馈给开发者。由于排查范围小,离修改时间近,定位问题将相对容易。针对已发现的问题的修复,也能很快得到验证。
降低交付风险:将漫长的集成工作分解,将未来的集成放在现在,将不可预知的集成变得可预知,避免在项目的后期因集成黑洞导致延迟交付。同时,由于主干被严格保护,因此产品在绝大部分时候(除非主干被破坏)都处于可交付的状态。
利于项目管理:持续集成,让项目的进度更加可见。持续集成的状态,就是项目的状态。通过持续集成系统,可以实时准确了解开发的状态和项目的进度。
持续集成有一个副产品,那就是将集成的压力一定程度上转移到开发者一侧:如果开发者的代码改动无法通过集成,那么代码改动就无法提交,开发者的工作就会受阻。因此,开发者有更大的动力去解决软件问题。另外,为了能够更快速定位集成问题,开发者也会逐渐倾向于提交较小的改动,而不是很大的改动。
在总工作量不变的情况下,改动越小,提交次数越多。高频率的代码提交,意味着高频率的集成。“天下没有免费的午餐”, 高频率的集成一般将意味着更大的资源消耗和更高的技术要求。
在有些时候,由于项目客观条件的限制,我们可能会不同程度上降低集成的频率。例如,将一个小时之内的代码提交(一次或多次)合并在一起触发集成,或者将功能上有依赖关系的提交合并在一起触发集成。但是我们要认识到这种退而求其次的做法是有代价的,那就是如果集成失败,定位是哪一个提交导致问题的过程会更复杂。
需要注意的是,代码提交阶段的持续集成(称之为前期集成)固然作用甚大,但是不意味着后期的集成就不需要了。代码合入之后的各个阶段,一般仍然需要进行集成(统一称为后期集成)。加强前期集成并不是要完全取代后期集成,而是要将绝大部分软件问题发现和解决在前期。这样,在后期发现的问题才能尽可能少,集成才能更顺利。
另外,前期集成是高度并发的,是同时发生在多个开发者各自代码提交之处的。这意味着,我们有可能同时解决多个集成问题。但是在后期,集成一般是串行的,往往“按下葫芦浮起瓢”:一个问题解决了,测试用例继续往前走了,才能发现另一个问题。
“积硅步,至千里;积小流,成江海”。持续集成将集成黑洞问题化整为零,各个击破。“笨鸟先飞, 未雨绸缪”。持续集成将软件问题发现在萌芽阶段。当然我们需要承认,持续集成本身并不能消灭任何软件问题,而是让软件问题更方便地被发现,定位和解决。持续集成的中心任务,都是为了让这个目的变得更容易,而不是更难。
我是肖哥shelwin,一个高质量软件工程实践者和推动者。欢迎扫描下方二维码,添加我的个人公众号测试不将就,获得更多自动化测试, 持续集成, 软件工程实践, Python编程等领域原创文章。