多级多粒度的持续集成 (graded and multi-scaled CI solution)

       也许对软件开发来说,最重要的已经不再是技术或工具、方法,而是过程。

在网上看了《跌跌撞撞的持续集成之路》(以下都称《路》),有一些想法,写出来和网友一起讨论。《路》中目标项目的持续集成迭代周期为一周,持续集成的任务流程为编译后台代码,后台代码单元测试,编译前台,部署,功能测试。任务流程决定了一个迭代周期内持续集成的实际执行时间。

《路》中所反应出来的问题其实就是实际集成执行时间和集成迭代周期之间的矛盾。理想情况应该是迭代周期能够包含执行时间,但实际情况却是,随着项目的扩张,当团队或代码基线变得很大时,持续集成的速度开始下降,执行时间往往有溢出迭代周期的危险。我听人说过敏捷开发对项目的规模是敏感的,它不太适于太大的项目,但是这个问题我们不展开讨论,我先不管是否敏捷开发,解决问题的思路有两个方向:

1.       延长迭代周期,让周期适应实际构建执行时间;

2.       压缩构建计划中的任务,缩短构建执行时间。

但如果单取以上两个方向任何一个去考虑,最终都会导致项目变得越来越不可靠。我的想法是两个方向同时考虑:裁减构建任务,定义多级构建计划;同时定义多粒度的迭代周期;不同粒度的迭代周期采用不同级别的构建计划。说的具体一点就是:如果构建任务集合里有编译、单元测试、功能测试、回归测试,而且都实现了自动化,则并不是每个构建计划都要包含构建任务全集,可以分为:

       编译级构建计划 只执行自动编译,目标是编译通过;

       单元测试级构建计划 执行编译和单元测试,目标是单元测试达到预期通过率;

       冒烟测试级构建计划 -执行编译、(单元测试)和冒烟测试(冒烟测试不知是否Sanity test),冒烟测试通过;

       功能测试级构建计划 执行编译、(单元测试)和功能测试(全集),目标是功能测试达到预期通过率;

       (回归测试级构建计划 -执行编译、单元测试和功能测试(全集)和回归测试,目标是功能测试和回归测试达到预期通过率;)

相应的,持续构建的迭代周期也被定义成:(小时)、天、周、()、以及Construction构建,不同粒度的周期里执行不同的构建计划。比如天构建可以执行编译级构建计划,发现的缺陷通过邮件自动及时的通知开发者,(有一个专门的角色)督促其在开发的同时改正这些缺陷。Construction构建可以执行回归测试级(甚至更严格的)构建计划,目标是可发布的稳定版本。

以上所有括号括起来的内容表示在具体实践中是可选的。总的来说,除了从CI工具和方法这点讨论解决问题的突破口,我们或者还可以从过程的角度探讨。下面将从几个方面详细论述这个多级多粒度的持续集成方案。

 

项目符合度

一个软件项目有一个总体的符合度(Compliance),这是我造出来的概念,也不知是否合适,所以也不能给出严格的定义,只能顾名思义。一个理想的软件项目总体符合度是100%。总体符合度在需求的角度表现为软件功能满足客户需求的程度,在设计和架构角度表现为设计和结构的健壮和自恰的程度,在集成和测试的角度则表现为缺陷数,缺陷越少,符合度越高。

一个实际的软件项目总体符合度理论上永不可能达到100%。从集成和测试的角度,我们用已修复或弥补的缺陷数占软件缺陷总数的一个百分比,比如80%90%甚至99%,来要求自己的工作,以保证软件符合度。

(集成和测试)符合度=已解决缺陷数/缺陷总数×100%

在项目之初,对符合度就必须有一个预期值,以便在整个项目执行过程中遵守。

另一方面一个实际软件的缺陷总数是不可知的,但如果假定测试充分,可以用已发现缺陷数逼近它,所以

       (集成和测试)实用符合度=已解决缺陷数/已发现缺陷数×100%

考虑到已发现缺陷数是缺陷总数的一个子集,我们会预设一个实际符合度比符合度高一些,比如90%99%甚至100%

 

       开发、重构以及修复缺陷的过程是不断引入缺陷的过程,集成和测试则是解决缺陷的过程。如下面的图所示,表示100%的红色虚线下的蓝实线表示预期符合度,在时间维上,项目有若干个迭代周期,则在一个周期内,开发和集成、测试交替进行,项目的实际符合度总是会慢慢的偏离预期值,之后又局部收敛到预期值。只要我们保证每个迭代周期内的局部收敛,就能期待符合度在整个项目期的总体收敛性。

持续集成和测试工作的目标就是为了保证符合度的局部收敛并最终保证总体收敛。我们现在的共识是没有、或者不充分的集成与测试工作,不能保证符合度的收敛,有可能像图中最下面一条曲线那样,实际值和预期值偏离越来越远,项目最终会被缺陷数淹死。

但另一方面,我们对过度的集成与测试工作的认识是不足的。个人认为:

l         实际执行所抱持的符合度是一个小于100%的值,应该允许一定量的缺陷留到下一个周期,甚至留到最终(这里还可以结合缺陷分级来定哪些缺陷可以留,哪些必需解决)。抱持100%符合度,或者整个项目过程中时刻抱持一个高符合度,不允许有波动,可能会导致过度的集成与测试工作。

l         如果任何缺陷都直接导致冻结新代码提交这一措施,在过度测试的情况下,其实质就是通过阻止新代码的提交来杜绝缺陷的引入。

l         在项目期间(或更确切的说一个construction期间),有一些功能或代码是不稳定的,不稳定代码引入的缺陷也可以看作是不稳定的。不稳定缺陷有可能在不稳定代码被修改、重构或删除的情况下消失。但如果在它们生存期内就解决了它们,这些工作对符合度的贡献很可能也会因为源代码的修改、重构或删除而降低甚至消失。这种情况在单元测试里表现尤其突出。

l         虽然我们可以要求项目在每一个短时间间隔内都抱持高符合度,从而让我们对总体符合度更有信心。但是那样做本质上只是缩短了符合度波动的周期,比如由一周之后重新收敛改进到一天之内就完成,因为只要有新代码的提交,就不能改变实际符合度发散的趋势。而符合度波动周期的长短对于项目最终符合度的贡献是低的。

所以过度的集成和测试工作可能缩短符合度波动周期,但项目可能会被过程中的缺陷压死。为了既不被缺陷淹死,也不被压死,我们需要适度的集成和测试工作。

下面是我的一些建议,或可减轻集成测试工作。

 

符合度要求

符合度是我们在项目期间始终关注的,对符合度的要求是可以分级的,比如可编译、可通过Sanity测试、可通过全面测试等等。符合度的要求可以和不同的迭代周期结合,比如天构建可以要求必须可编译,周(或更长周期)构建可以要求可通过Sanity测试,而为Construction的构建则必需执行可通过全面测试的要求,或者可以发布稳定版本的要求。低粒度构建就是为高粒度构建打下基础,也就是说小迭代周期构建在较低的符合度要求上的收敛,是为了避免符合度在大周期上偏离得完全不可收拾,否则我们不想被淹死也要被淹死了。反过来说,如果用Construction构建的要求来要求天构建也是不可取的,很可能被缺陷压死。

 

行动的级别

这里说对缺陷采取什么样的行动。这可以分为对单个缺陷的策略,和对一个构建周期内所有缺陷的策略。对单个缺陷是基于缺陷分级的,这个不具体展开。对于构建期内缺陷的总体策略,大概可以分为:

Fix while going on

这种级别下,开发者不必停下来专门抽时间修复,可以一边开发一边修复。但是缺陷的Owner必需被通知到,并被督促尽快修复,自动构建的邮件功能可以很好的做到这点。这一行动级别可以用于要求为“可编译”的构建。

Stop and fix a certain percent

代码提交被冻结,直到修复的缺陷达到所有发现缺陷的一个预期百分比,代码提交才恢复。这一过程可以基于缺陷的严重程度分级,比如Major以上的都必须解决,Minor的可以留到下一个构建周期解决,但必需保证修复百分比。在此需要有一个角色对每个缺陷进行分析并决定修复的策略。

Stop and fix all

和前一个级别不同的是修复百分比为100%

在后两个级别中,代码提交冻结的决策,不必依赖于自动构建是否发现缺陷和缺陷数的多少,而是可以根据构建本身的重要程度。这样就可以事先决定,并手动实施冻结。

 

迭代的周期

其实前面已经提出了构建采用多粒度周期的方案,不同的迭代周期采用不同的任务组合,不同的要求和行动级别。

 

一个专门的角色

贯彻这些标准和过程,对单个具体缺陷的行动级别做出决策,督促和协调程序员做出行动等。

 

关于Unit test

Unit test是个好东西,我在很多地方都看到别人说它的重要性,我也这样认为。我重点讨论Unit test让我为难的方面。

首先,我决定写Unit test,前期的工作量很大,许多人都主张源码未动,Unit test先行。我只能同意这一点,但在实际中我是做不到这一点的。而且正常的Unit test代码量甚至多于源代码数倍,虽然可能并不是所有的都是前期写好的,但还是能够反应出前期工作量的大!

其次是Unit test期中维护的工作量也很大。之所以这样,第一点其实是和我做不到“源码未动,Unit test先行”的原因是一样的――老实说,即便是详细设计阶段,我也只是对主要类,以及每个主要类要提供的功能有数。具体每个类里面要确切定义哪些函数,以及功能在这些函数里怎么分配,我至少没有一个清晰的概念。所以类结构在编码过程中是不稳定的,很可能一个主要类因为size的关系在实际编码时被分为几个类,函数也同样。总之我觉得我的编码过程同时也是一个重构的过程,编码在肯定、否定、再肯定、再否定中不断循环。所以在源代码不稳定的前提下,想要稳定的unit test我觉得很难。另一方面,我们采用的是增量迭代的开发过程,源代码也会随着功能的增加和改进而改变。所以说unit test 其中的维护工作量大。

Unit test实现自动执行是想对容易的事情。但如果执行结果出错,检查root cause的工作又是一个负担,因为问题到底在Unit test里还是在源代码里还两说-有没有人对unit test事先做test的?

最要命的是,在我们花了那么大的精力去维护Unit test的情况下,因为源代码不稳定,处于修改之中,unit test随之不稳定。我们的大量的维护工作的贡献度很可能因随后的修改几乎为零。

所以我建议把对unit test的处置权交给开发者,反正他们要在编码过程中用unit test检验自己的代码,维护工作可以有一个自由度。同时天构建可以持fix while going on的要求自动执行unit test,发现失败的情况,可以及时通知开发者。Construction构建则可以采用第二级要求,防着某些偷懒的程序员,也是为了以免unit test失控。

 

其实我是CISCM的初入门者。这篇帖子里的很多词汇比如项目的符合度之类的,只是我自己造出来的,再比如文中的集成和构建很多地方是混淆的。写得并不专业,时间关系也没有查阅资料以符合业界标准。文中提到的很多东西也不是我的独创,很多在实际中已经在用了。我也没有作为CISCM的专业角色操作过任何项目,以上这些只是结合我作为程序员和设计师所参与的软件项目中观察到的和体会到,并参考有关网上文章,写出来的,因此水平有限,其实用性有待证明,也请大家多指正!

 

参考网络文章

“持续集成”也需要重构——持续集成实践在Cruise开发过程中的演进http://www.kuqin.com/software-engineer/20090401/43345.html

让开发自动化: 持续集成反模式http://www.ibm.com/developerworks/cn/java/j-ap11297/

 

:跌跌撞撞的持续集成之路 http://www.infoq.com/cn/news/2008/09/road-of-ic

猜你喜欢

转载自qianxiangdong.iteye.com/blog/397518
今日推荐