Subscribe to XML Feed

07 Mar 2009

持续集成是一种思维方式

我的工作内容决定了我对于持续集成这一话题有着浓厚的兴趣。在工作之余,我常常会拉着身边的朋友们问“你们是怎么做持续集成的?”

通常,我会听到类似于这样的答案:

  • 我们在用cc/cc.net/cc.rb/TeamCity/Hudson……
  • 我们在用Subversion/TFS/……管理源代码
  • 我们有一部分自动化的单元测试,每次提交代码以后都会运行
  • 在正式发布产品之前,我们会先有一个内部的测试阶段(UAT/staging),只有当产品质量通过了内部测试以后才考虑向外发布
  • ……

这些回答看起来就像是标准答案一样,曾经一度让我感觉很困惑,因为在和这些朋友的深度交谈中我往往会发现不少持续集成实施方面的缺陷,但是这些缺陷却很难通过了解他们采用哪些持续集成实践而判断出来。难道我们不能简单地通过实践层面来判断一个团队的持续集成能力吗?

经过一段时间的思考,我找到了问题的答案:判断一个团队的持续集成能力,不能仅仅看他们采用了哪些持续集成的实践,而是要看他们能不能运用持续集成的思维来解决他们所遇到的问题,并且持续地改善他们的持续集成实践。

这样说可能太抽象了,让我来举一个例子。前段时间,我的一个朋友谈到了他们项目上在采用的一些持续集成实践。他提到了daily build、自动化测试、分阶段部署以及持续交付等,听上去非常不错,而且产品交付也还算顺利。然而,他也提到了一个问题:每次临近发布的时候,他们就把持续集成服务器的自动build功能关掉,变成手动build。当团队想要一个build的时候,tech lead就要和QA lead坐下来,讨论系统目前的状况是否适合做一个build。双方会把自从上次build以后的修改都审查一遍,凭经验决定当前做build风险是不是很大。只有当感觉风险不大的时候才去生成build,然后进入复杂的人工测试阶段。

这个过程听上去非常奇怪,到了发布阶段做事小心一些是对的,但仅仅依靠经验来判断产品的质量似乎太惨了些,为什么不相信自动化测试呢?他解释说,项目上的自动化测试并不多,覆盖率也并不理想,特别是一些比较重要的系统行为并没有被涵盖到,因此大家在感觉有压力的时候更倾向于相信人工测试的结果。

那么为什么不增加自动化测试呢?他的回答也很无奈:自动化测试写起来成本不低,项目的交付压力又很大,大家没有足够的时间去写自动化测试。

说到这里,我想在实际开发项目中摸爬滚打过的朋友都不会感到陌生。在现实的压力面前,我们往往选择最为“安全”的办法,那就是“头痛医头,脚痛医脚”。从表面来看,这样做是无可厚非的,而且似乎与持续集成的关系也不大,然而一旦仔细分析我们就会发现,对于类似这样问题的态度却恰恰反映了一个团队真实的持续集成能力。

为什么呢?

原因在于,持续集成和我们常常挂在嘴边的CMMI/Scrum/Agile等过程模型一样,从本质上来说属于一种思考问题的方式。CMMI/Scrum/Agile等过程模型关注于对过程进行分析和控制,它们运用的是一种过程的手段/语言。比如说,当项目无法顺利交付时,负责实施CMMI过程的人员(比如说项目经理等)通常会去审视项目上的监督机制,看看是不是可以在某一方面(比如说测试覆盖率)增加数据收集的力度;而负责实施agile过程的人员则有可能去思考是不是项目上的沟通出了问题,是不是出现了过多的 技术债务 等等。在找到问题的根源后,他们往往会通过改善过程来预防问题的再次发生。

同理,持续集成也是一样的。当相关的问题出现时,持续集成能力强的团队会在解决具体问题的同时还去思考:这些问题为什么会出现?我们应该怎样做才能避免类似的问题再次出现?有什么办法能让问题一旦出现就能很容易被发现?相比之下,持续集成能力弱的团队往往更关注一个个具体问题的解决。他们的确有能力解决一个个的具体问题,然而非常可惜的是,由于没有从持续集成的角度来审视这些问题,这些问题在产品发布过程中还会再次出现,如果问题比较严重的话,还会把整个团队拖进一个痛苦的恶性循环中。

那么,有办法能解决这个问题吗?

答案是有的,但是软件开发里没有 银弹, 治标更要治本,想真正解决问题就必须找到产生问题的根源。比如说,在上面提到的这个例子里面,问题的根源就在于没有足够的自动化测试。

说到这里,我们遇到了阻碍持续集成实施的一个难点。我不想在这里写很多关于如何编写自动化测试的内容,因为在我和很多同行的交流中,我感觉阻碍推行自动化测试(或者任何其它持续集成实践)的难点更多是在文化和交流上,而不是在技术上。我只是想介绍一下Cruise团队是怎样面对这个问题的,希望能给大家提供某种参考。

Cruise有着一个10人左右的团队,在这个团队里只有一名全职的QA,这个比例可能比绝大多数的团队都要低,但是从去年7月至今,我们已经高质量地发布了1.0、1.1和1.2三个版本,而且发布的过程都非常顺利。这在很大程度上归功于我们所写的大量测试。在Cruise的代码库里,我们有着单元测试、集成测试、系统测试和性能测试。我们采取了一系列的措施来保证这些测试会一直为我们提供最大的价值:

  • 我们采取TDD的开发模式,所以单元、集成测试往往会先于产品代码而出现
  • 每一个新功能的开发都必须包含完整的自动化系统测试。在我们的项目 管理工具 中,任何一个新功能在可以交付给QA做人工测试之前,必须要经过自动化的系统测试。开发自动系统测试的时间有时候甚至会超过开发相应的产品代码的时间
  • 每当遇到一个bug的时候,我们都会分析这个bug产生的原因,除非该功能很不适合做自动化测试(这种情况非常少见),否则我们都会增加测试来确保以后该问题不再复现
  • 如果遇到bug后,我们发现现有的测试效果不好,我们会重写那些测试。比如说,一段时间里我们遇到了不少与版本控制系统集成相关的bug,我们发现这部分功能更多地是在做mock测试,虽然mock测试速度快,但在速度和质量之间我们更倾向于后者,所以我们把这部分测试全部重写成了真实的集成测试,从此这部分功能的质量问题大幅减少
  • 如果系统中某个部分存在着性能问题,我们就增加相应的性能测试。我们甚至自己开发了一套简单有效的性能测试框架

问题的关键在于,这些措施都不是从第一天开始就有的,其中的绝大多数都是我们在解决了一个个具体的问题之后陆续做出的调整,而这恰恰是实施持续集成的根本所在:衡量一个团队的持续集成能力,不是看他们目前都在做些什么,而要看他们在遇到问题之后能不能举一反三,能不能对持续集成过程做出相应的修改来预防更多类似问题的出现。换句话说,持续集成不仅是一种工具,也不仅是一套方法,它更是一种提高产品质量、提高项目交付能力的思维方式。一旦团队拥有了这种正确的思维方式,那么遇到的问题越多,解决问题的办法也就会越多,团队的交付能力也就会越强。

View Comments
28 Dec 2008

一次有趣的开发经历

周五下午和同事Pavan一起结对编程,为Cruise 1.2增加svn external的支持。几个小时过后,我们提交的代码漂亮地通过了所有的单元/集成/验收测试。在这个过程中,我觉得有一些很有趣的细节非常能体现出我们的工作方式,所以现在把它们列出来:

集体重构

我们的修改是以前一段时间项目里大伙凑在一起做的一些修改为基础而进行的。在Cruise项目上,我们有一个不成文的习惯,就是隔一段时间大伙就凑在会议室里,拿一台笔记本连上投影仪,找出一段写的不好的代码做集体重构。每次开始的时候会有人负责提出一个问题,例如一段写的很糟糕的代码,或者一个看上去很牵强的设计,然后大家一起集思广议想解决办法,并由一个人拿着电脑对着投影仪直接修改。在这个过程中有时候写代码的人思路会中断,这时就会有人抢过电脑来继续写。这种方法的效果非常不错,特别是当大伙不断对一个问题追问“为什么”的时候,往往会让我们原本非常狭窄的思路一下子拓宽起来。

分布式版本控制

Cruise选择Mercurial做为版本控制系统。大家在集体写了svn external的初始代码之后,在一台笔记本上留下了一个patch。我和Pavan要做的第一件事就是把这个patch迁移到我们工作所用的pair station上来。方法非常简单:首先把那个patch通过scp拿到本地来,然后运行hg qimport指定patch文件的存储路径就可以了。当然,还可以用hg pull把那台笔记本上的修改都pull过来,或者利用hg transplant把那个指定的版本pull过来。这几种方法都可以达到我们的目的。前一种操作多一些,但会把拿过来的patch加入到本地的 queue中,是我个人最习惯的用法;第二种操作很简单,但是有可能pull过来的修改过多;第三种操作简便,效率理想,只是并不会把拿来的patch加入到queue里,我们平时会穿插着使用。

工欲善其事,必先利其器

我们必须要在本地创建相应的测试环境才能验证svn external功能,但是我们并不想每次都手动地去svn co, svn propset, svn ci, svn up, svn propget…。这些重复的操作应该而且也非常适合加以自动化。解决办法非常有趣:我们在本地创建了一个新的目录,在里面写了几个shell脚本。第一个用于创建svn external环境,其中包括复制出一个用于测试的svn repository,把它check out到一个指定的客户端目录,在客户端目录里执行svn propset/ci来创建svn external,以及执行svn up来更新客户端目录。第二个脚本用来启动Cruise Server和Agent。第三个脚本用来停止Cruise Server和Agent。有了这几个脚本,我们就能非常方便高效地运行测试了。当然,我们没有忘记把这些脚本都提交到Cruise的 repository里去。

说到工具,我还要顺便提一下我们的开发环境。Cruise团队里的每一个pair除了各自拥有公司配发的笔记本以外,还共同拥有一台非常强大的开发机器(Core 2 Quad CPU * 4, 4G RAM),采用双24寸液晶显示器。操作系统用的是Ubuntu,开发工具用的是IntelliJ。这些强大而灵活的工具让我们能够最大程度地激发工作热情,提高工作效率。

测试,测试

我和Pavan在测试过程中发现了一个bug,凭经验我们很快找到了导致bug产生的一个方法。不过,我们并没有急于把它消灭掉,而是给现有的方法增加了一个测试。当然,这个测试是不通过的,所以我们接下来的目标就是改正代码,让测试通过。几分钟过后,我们消灭了bug,并且使得系统的测试覆盖率又小小地增加了一点。:)

持续集成与及时反馈

当我们通过了本地的所有单元和集成测试以后,就信心满满地把本地的提交push到了汇总的mercurial repository里,很快,我们自己UAT环境里的Cruise Server就检测出了这次代码提交,并且开始进行集成。我们的第一个stage是在Linux和Windows平台下并行运行所有的单元测试,然而这个 stage中的一个Windows job却在几分钟后失败了。通过阅读Cruise给出的失败信息,我们发现刚刚提交的代码没有考虑带空格的URI这一种情况,而这种情况在Windows 下非常常见。获得了这个反馈,我们接下来的工作就是在本地增加一个处理带空格的URI这种情况的测试,看它运行失败,然后修复,提交。不一会的功夫, Cruise显示所有测试全部通过!

View Comments
28 Jul 2008

Cruise 1.0正式发布!

今天,我所在的项目──Cruise──正式发布了第一个公众版本! 大家如果感兴趣可以点击 这里 获得更多信息:

Cruise提供30天的免费试用,并且在试用期过后继续提供两个agent的免费使用许可,欢迎大家使用并提出宝贵意见!

View Comments