持续集成是一种思维方式
我的工作内容决定了我对于持续集成这一话题有着浓厚的兴趣。在工作之余,我常常会拉着身边的朋友们问“你们是怎么做持续集成的?”
通常,我会听到类似于这样的答案:
- 我们在用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测试速度快,但在速度和质量之间我们更倾向于后者,所以我们把这部分测试全部重写成了真实的集成测试,从此这部分功能的质量问题大幅减少
- 如果系统中某个部分存在着性能问题,我们就增加相应的性能测试。我们甚至自己开发了一套简单有效的性能测试框架
问题的关键在于,这些措施都不是从第一天开始就有的,其中的绝大多数都是我们在解决了一个个具体的问题之后陆续做出的调整,而这恰恰是实施持续集成的根本所在:衡量一个团队的持续集成能力,不是看他们目前都在做些什么,而要看他们在遇到问题之后能不能举一反三,能不能对持续集成过程做出相应的修改来预防更多类似问题的出现。换句话说,持续集成不仅是一种工具,也不仅是一套方法,它更是一种提高产品质量、提高项目交付能力的思维方式。一旦团队拥有了这种正确的思维方式,那么遇到的问题越多,解决问题的办法也就会越多,团队的交付能力也就会越强。

