Subscribe to XML Feed

07 Apr 2009

单纯的过程改进真的能提高开发团队的交付能力吗?

今天中午和一位刚从客户那边做完敏捷咨询的同事聊天,这位同事和团队利用四个月的时间把客户的一个几十人的产品团队领进了敏捷的大门,教会了大伙怎么样进行敏捷需求分析,开展敏捷项目管理;手把手地和大伙一起用TDD的方法开发代码,编写测试,进行重构;用Cruise搭建起了持续集成平台,把原本零散滞后的编译集成过程自动化起来;甚至几个人特意为客户开发出了一套在他们开发平台上缺少的测试框架…… 短短的几个月下来,这个开发团队的交付能力产生了质的变化,而且更为关键的是,大家都看到开发团队是在延着一个正确的方向上走。

上个周末,我和在另位一家公司做tech lead的一位朋友也聊起了类似的话题。她所在的项目已经运行几年时间了,然而她却还在为测试力度不够、设计质量不高、持续集成效果不佳等问题而头疼。而且我在谈话中得知,她们公司中的其他项目也都有着类似的问题。由于在公司层面很少能得到什么实质的支持,tech lead们只好自发组织起来,互相交流,以期摸索出一些有用的东西来解决问题。

同样是开发团队,为什么一个团队仅仅用了四个月的时间就脱胎换骨,而另一个(其实是多个)却常年得不到实质的提高?

原因是有多方面的,但我今天想说的是,软件开发归根到底还是一种工程层面上的活动。再好的项目管理方法,再好的项目管理人员,再好的项目监管平台,如果没有工程层面上的重视和提高,都只能是白费。

上面例子中的后面一家公司在两年前开始开始实施CMMI,并且顺利通过了CMMI五级认证。公司给项目运作从头到脚建立了一套较为完整的管理体系,从需求过程管理到设计过程管理,从测试过程管理到开发过程管理,从资源管理到风险管理,无不涉及。项目经理们每周都要填写不少的文档,公司甚至还成立了专门的监管组织,专门负责以CMMI的标准来定期审核各项目的运作情况;在过程改进的同时,公司还在人员建设上下了功夫,陆续招入了一些有经验的项目经理。

然而,过程改进层面上的繁荣并不能掩盖开发团队工程能力的不足。随着公司的战略转型,大量经验欠缺但成本较低技术人员的流入更加剧了这一矛盾。开发人员没有写测试的意识和经验,项目的交付压力使得大家尽可能快地写代码,而没有足够测试覆盖率的代码只能是越堆积问题越大;很少有项目能顺利地进行自动化系统测试,缺少了自动化测试的帮助,测试人员只能隔段时间进行一次“人肉回归(manual regression testing)”,耗时耗力,而且很容易出错;大多数的团队都还没有正确地应用持续集成,少数已经应用了的通常也只限于进行自动化编译和打包,更不要提持续发布和版本管理了;项目人员在拥挤、空气混浊的环境中办公,很多项目根本没有一块独立的白板;很多开发人员从来没有用过重构工具,很少有人关注在工具层面上提升生产效率,甚至很少有人能熟练运用开发工具的快捷键,而只是一次次低效率地用鼠标去点击菜单……

说到这里,我没有任何贬低的意思,因为我也经历过类似的环境,也有过同样的困惑和迷茫。在经历了一个个的交付项目,一遍遍地重复上面的种种问题之后,我曾经对软件开发失去过信心。一直到加入ThoughtWorks,和身边的诸多高手共事以后,我才亲身体会到,软件项目归根到底还是一种工程层面上的活动,任何的社会性因素都不能够也不应该喧宾夺主。开发人员只有学会了正确地写单元测试,才能保证开发出来的代码在单位层面上是工作的,也才可以尽早地暴露潜在的问题;只有积累了大量好的测试,才能在系统演变的过程中持续获得回归回馈,否则时间越长,代码越难维护;开发人员只有学会怎样重构,才能不断改善系统的设计,让系统始终处于一个可工作、可调整的健康状态,而不是过早地病入膏肓,任何改动都可能会伤筋动骨;团队只有掌握了正确的自动化测试技能,才能开发出稳定、有效的系统测试,从而帮助测试人员分担测试负担,以便让测试人员把有限的精力投入到探索和创造上面;团队只有正确地认识、应用并且不断重构他们的持续集成过程,才能持续不断并且快速地获得系统质量的反馈,并且参照这些反馈来不断地改进和完善自己的开发过程……

然而非常可惜的是,很多的组织只注意到了开发过程的改进,用“大跃进”的思路,期望通过自上而下的管理手段来解决问题,而忽略了最为重要的工程层面的努力,其结果也就可想而知。软件开发不是工业生产,开发团队如果只是周期性地写写表格、做做汇报、分析一下交付风险,制定一些应对措施的话,其交付能力是不会有什么实质的提高的。

View Comments
16 Mar 2009

用虚拟机+mercurial来合理分配运行测试时间

在开发过程中,我们常常需要在提交代码之前先在本地运行测试,看一看当前修改的质量如何。可是问题是,一旦测试耗时过长(比如说超过15分钟),那么必然会对开发进程造成影响,因为通常在本地运行测试的时候我们都会暂时停止开发;而且过长的测试时间也势必会影响持续集成的频率,毕竟我们不想花太多的时间在等待上。

在Cruise上,我们运行本地测试的时间大约在15分钟左右,这在很大程度上阻碍了我们的持续集成步伐。解决这个问题,一方面是治本,那就是找出并解决影响测试速度的瓶颈来,我在github上有一个 项目, 专门用来做这个分析,最近一段时间会发布第一个版本;另一方面则是治标,也就是找到一个办法来让测试运行不影响本地的继续开发。

想达到后一个目的其实并不难。最简单的办法就是让测试运行在一台虚拟机上,这样测试和开发就互相不影响了。有了这个想法,我和同事Pavan就在我们的Ubuntu开发机器上装了一个VirtualBox虚拟机环境(另一个备选的方案是KVM,但是考虑到VirtualBox的安装和配置更简易省时,我们还是选定了后者),在其上创建了一个Ubuntu 8.10服务器版的虚拟机,分配了1G内存(我们的开发用机有4G内存!),然后在其上安装好运行测试所需的环境(mercurial, git, ant, nant, ruby, rake…)。

接下来要做的就是把源代码从开发机器上转移到虚拟机了。由于我们用的工具mercurial对点对点协作模式有着天然的支持,因此做到这一点非常容易:我们首先在开发机器上运行hg serve,使得它可以对外提供源代码访问服务;然后再在虚拟机里做hg clone,把开发机器上的修改同步到虚拟机上。整个过程结束后,我们就可以在虚拟机上直接运行ant命令来运行测试了。

如果工作机器上后续又出现了新的代码修改,我们的操作流程稍微有一点变化。由于我们在开发过程中都是使用 hg queue 来管理本地修改的,每次更新queue都会导致开发机器代码仓库中被修改的那条记录的版本发生变化,因此直接用hg pull -u更新到虚拟机的话就会产生版本冲突现象。解决的办法也很简单,就是在虚拟机里运行hg out,把相对于开发机器多出来的版本(其实也就是在开发机器上被更新过的版本)用hg strip命令消掉。然后再运行hg pull -u就不会出现冲突了。

通过把测试工作转移到单独的虚拟机来进行,我们节省了大量的原本用于等待的时间,从而很大地提升了团队的工作效率。

View Comments
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

Older Posts

团队设计能力的交接 18 Aug 2008 Comments