拥抱变化——持续集成(CI)实践心得

  1. 云栖社区>
  2. 博客>
  3. 正文

拥抱变化——持续集成(CI)实践心得

cnbird 2013-03-07 16:55:34 浏览480
展开阅读全文

引子

    记得刚加入趋势开始开发工作的时候曾被告知,趋势有一套auto build的系统,会每天夜里自动把当天check in的代码进行构建,生成QA可测试的build。每个RD都得小心提交code,因为项目结束的时候会看auto build的失败率。可是构建失败总是在所难免,尤其是每次要提交candidate build给QA做full cycle测试时,总是在最不该发生的时候发生最不可能发生的事情,至今我还记得为了按时出一个合格的candidate build,熬夜等build的经历。

    Martin Fowler大师在2000的一篇文章中,最早提出了Continuous Integration(CI,持续集成)的概念。此后,CI作为软件开发的最佳实践已经被很多的团队所采纳。从今年年初开始,我们开始了一个项目新版本的开发,由于项目具有的不确定性,以及开发周期不是特别紧,我们团队决定在开发流程和质量控制上尝试着做些改变,在趋势,Change是深植入企业文化的一个特质,这里的Change不单单只包括老板和管理团队自上而下的Change,还包括开发团队和个体,只要你有想法,你都可以尝试着去改变。(之前博文中的测试驱动开发的半年实战心得,也来自我们团队另一个成员在该项目中对于TDD的新尝试)

 

简介

    正如Martin Fowler所说,CI要求开发团队能够频繁地集成开发和测试工作,以便尽早发现问题,减少项目风险。这里的关键是“频繁”,如果CI也只能每天在夜里做一次集成,那和原来的daily build又有什么大的差异呢?

    CI其实是由一系列的最佳实践所构成:

  • 源代码的版本控制和管理
  • 自动化构建
  • 自动化测试
  • 代码审查
  • 自动发行和部署
  • 持续反馈
  • 等等

    通过持续地对代码和测试进行尽早频繁地集成以尽早发现问题,而为了能够频繁地集成,又对自动化提出了很高的要求,毕竟只有机器自动完成才有可能在一天里集成若干次(如果需要专人来负责这件事情,第一老板不会同意,成本太高了,其二人总是难免有出错的时候,结果可能集成本身花费的时间超出了开发的时间,这就得不偿失了,其三,由于人的适应性和“包容性”,也会让规范很难形成并遵守)

 

实践

    既然CI包含了这么多实践,我们决定采用渐进的方式来开展,罗马不是一日建成的,在这个项目中,我们依次展开了:自动构建、持续反馈、自动测试和自动部署等实践,其中没有包含代码审查的部分。由于趋势已经有一套成熟的版本控制和构建系统(采用Perforce进行版本控制),这使得我们的自动构建变得相对容易,所以我们以自动构建为基础,展开如下的一些实践:

    - 增量编译(Incremental Build):CI系统会定期监视Perforce(每5分钟),一旦发现有新的代码check in,就会触发增量编译,取出最新check in的代码进行编译,完成后给check in代码的人发出邮件通知(成功或者失败),如果编译失败,对应的owner必须优先解决,否则CI会每隔一段时间(5分钟)再次尝试,一直到成功为止。由于增量编译很快,Developer一般在check in代码几分钟后就能收到通知,如果失败可以立刻予以修复。

    - 单元测试(Unit Test):CI系统在增量编译通过的基础上,会通过我们的测试框架自动调用每个模块的单元测试,同样也会发出测试报告,如果测试失败也要优先解决。这里由于我们项目目前还没有太多单元测试的case,因此我们第一步先把测试框架搭建好,然后对项目中新的代码推行单元测试,而对于legacy代码,则视情况补充case。

    - 自动发行和部署(Full Build & Deployment):CI系统会定期(3小时)执行Full编译和打包,最后生成可安装的发行包,然后通过虚拟机自动部署并安装到远端的虚拟机上。由于完整的编译和打包操作很费时间(半小时以上),因此没有必要对每次check in代码都执行。通过自动发行和部署,CI系统可以尽早发现打包和安装过程中的问题,并尽早予以解决。

    - 组件测试(Component Test):相对于单元测试,组件测试更关注于各个组件间接口部分的测试,此外,单元测试一般要求没有外部环境的依赖,比如数据库,DNS等,如果必须有这些依赖必须通过mock的方式解决,而组件测试则运行在真实的产品环境里,因此数据库、DNS等外部依赖都可以作为测试的输入输出。这部分和单元测试一样,由于没有现有的基础,因此我们优先搭建好测试框架,然后对新代码进行测试,对于legacy代码则视情况补充。

    - 自动化功能测试(Feature Test):这是我们现有做的做多一部分,趋势QA会对很多的功能测试case进行自动化,自然在CI系统中也会把这部分纳入。我们采用每天一次的周期来运行这部分测试。

    - 覆盖率(Coverage):既然在CI里包含了这么多的测试的内容,那如何衡量测试的效果和标准?覆盖率便是一个可行的量化标准。因此我们在CI中也加入了覆盖率的统计,通过代码覆盖工具,我们可以得到测试运行的代码行覆盖率和条件覆盖率。这些结果也会被汇总到CI报表里,这样我们随时看到测试的效果和历史数据的改进。

    - 反馈及可视化:在CI里还有一个很重要的部分就是反馈和可视化。有效的反馈机制可以让产品的问题尽早暴露并通知到合适的人。而同时通过CI中的可视化可以展示很多信息,比如测试用例的数量,覆盖率,成功和失败构建的次数,代码check in次数等等。通过可视化,可以达到:1、让团队成员了解项目现状,增加成就感(想想如果你每天可以随时在报表里看到由于自己的工作而增加的部分,是不是很酷?)2、让manager随时了解团队现状,减少项目风险。

 

工具

    工欲善其事,必先利其器。这里介绍一下我们在CI实践中用到的一些工具以及选择的理由。

    CruiseControl,我们选择它开始CI系统,在选择它之前我们也考察了其他一些CI系统,最终CC胜出也是有很多理由的:

    - 首先很重要的理由CC是免费开源的系统,我们在CI上也刚刚开始尝试,不太可能去考虑那些很贵的商业系统

    - 其次,CC提供了很多的SCM工具的集成,比如Perforce,SVN,Starteam等等

    - 此外,CC有非常好的扩展性和定制能力,在CC的build loop里可以添加不同的Task来增加额外的支持,比如我们的很多测试task、覆盖率等都可以很方便地加入。

    - 最后,CC有着很多的用户,因此在网上可以找到比较多的支持文档。

    Python,我们选择python作为我们很多定制task的编制脚本,同时也使用python作为测试框架的脚本(以python测试框架Nose为基础)

    CppUnit/xUnit,这个不用介绍,单元测试必选的测试框架。

    Bullseye,使用bullseye作为覆盖率工具,它可以统计行覆盖率和条件覆盖率,并且有很好的命令行脚本支持。

    VMWare,组件测试和功能测试都需要自动部署到虚拟机执行,因此我们选择了简单好用的VIPerl作为VM的控制脚本。用它进行VM snapshot管理任务。

    此外,还用到了很多附属的工具集,比如ssh,expect,ant,XML/XSLT,等等。在构建CI系统中一个体会就是,尽可能使用现有的工具,通过粘合胶水(比如python)将它们串起来完成复杂的任务,从头造轮子很少是CI最有效率的选择。

 

心得

    最后,谈谈在这次CI实践中感受到的一些心得:

    CI和process及agile:敏捷编程中要求在每个小迭代中都有交付件,因此要求每个迭代都有完整的集成及测试工作,因而CI是一个很好的敏捷实践,用以保证交付件的质量。如果没有很好的CI,很难做到真正的敏捷。此外,CI的引入也会对现有流程形成一定的影响,一个实际的例子就是:以前RD总是在每天晚上下班前把当天完成的代码check in,而现在则是,完成了一部分就立即check in一部分,并等待几分钟,确保check in的代码不会让CI失败。

    CI和testing及automation:其实在前面的实践中也已经看到,CI中包含了很多的测试实践,比如单元测试、组件测试、功能测试、系统测试等等。Integration不只是Compile,更多地是通过测试来保证质量。这对RD和QA都提出了更高的要求,首先,持续意味着我们必须要保证测试的一直可用,在实施CI之前,我们也有单元测试,但单元测试往往在进入alpha或beta后就再也没人关心和维护了,在项目结束时甚至单元测试的程序连编译都不能通过。其次,自动化的要求意味着必须要更好地去考虑产品设计、实现、以及测试的设计工作,一个低耦合的架构才有可能更多地自动化,糟糕的设计工作会让自动化根本无法进行。

    CI和cross-platform开发:趋势很多项目都有多个平台的版本,因此对软件的跨平台开发也有很高的要求。那CI对跨平台有什么意义呢?如果我们在多个开发平台上都有响应的CI系统,那我们在开发任何一个平台的时候,新增或者修改的代码都可以及时通过其他平台上的CI系统得到尽早的验证和反馈。这样,通过CI可以更好地要求开发人员考虑跨平台的需要,不能因为一个平台的代码而让其他平台的CI失败。

    “持续”:我想对CI里“持续”的理解可以从两方面来谈,首先是持续地集成产品,尽早地发现问题;其次,也可以把这里的持续理解为持续改进,正如前面说的,CI里包括很多的实践,我们不可能一下子引入全部,这就要求我们有持续改进的sense,持续地引入新的实践(比如加入代码审查等)、持续地加入新的case、持续地完善CI和process,在改进的同时,CI又很好地保证了已有部分的长期有效,不过像猴子摘西瓜那样,缺少历史的积淀。

    企业文化和公司的支持:最后一点心得,和CI关系不大,但在任何公司、任何组织中,要想能不断改进、尝试新的实践和流程,必然离不开组织和制度的支持。我们在实践CI过程中,manager们给了团队很多的自由,可以充分去发掘,同时允许失败,这是任何一个实践能够有所收获的必备前提。

    最后,推荐一本关于持续集成的书籍:Continuous Integration: Improving Software Quality and Reducing Risk(持续集成:软件质量改进和风险降低之道),它对CI进行比较全面的介绍,可以从这本书里开始对CI做个全面的了解。

    CI并不是软件开发的银弹,它也并不尝试解决软件开发中固有的很多问题,但通过采用CI,可以更好地控制和降低风险,并能更好地保证团队和流程走在不断成功和改进的正确道路上,从而让我们有更大的信心去release产品, refractor代码,agile流程。

    拥抱敏捷、拥抱变化、拥抱CI!

网友评论

登录后评论
0/500
评论
cnbird
+ 关注