也许由于软件行业固有的“高科技”特性(或者说,软件人固有的顾影自怜),软件项目的管理和过程控制中从来就不缺少形形色色的工具。在一些大型的、“正规的”软件企业中,配置管理要通过ClearCase,软件设计要使用Rose画出一大堆精美的图形,压力测试要用LoadRunner来跑……当软件公司的老板们痴迷于“形式化管理”与漂亮的报表和文档时,这些昂贵的商业工具着实在他们那里得到了不少的青睐。
幸运的是,越来越多的软件开发者和老板开始意识到,软件项目采用的方法没有一定之规,不同的项目、不同的团队需要选择不同的开发方法。而工具,则如同三棱镜般折射出方法学的身影——用着微软的TeamSystem就很难不遵循微软推荐的最佳实践,同样RUP在没有Rational工具支持的情况下也难以实施。于是,选择开发过程工具,很大程度上就成了选择开发方法的一个副产品。
对 于身处激烈需求变更风暴之中的企业应用开发者,如何步步紧跟客户的真实需求、如何确保时刻为客户提供最大价值是他们每天冥思苦想的问题。此时注重交流反 馈、以客户价值为驱动的各种敏捷开发方法就成为了他们自然而然的选择。而有趣的是,开源的过程工具也大多与敏捷方法最为适应。看似偶然,其实却有其道理: 开源项目更少受到种种政治因素的影响,生存的环境又有更多的不确定性,因此也更加强调时刻保证最大化的客户价值。而这种思路,与敏捷方法是不谋而合的。再 加上,推崇敏捷方法的那些“实用主义程序员”们往往也正是开源社群的积极分子,所以适用于敏捷项目的开源过程工具尤其容易找到也就不足为奇了。
敏捷的开发者们是幸福的,因为他们拥有众多优秀的开源工具可供选择;敏捷的开发者们又是痛苦的,因为他们必须在乱花渐欲迷人眼的工具丛中找出适合自己的一组工具栈,并将它们与自己的管理策略糅合成一个完整的开发过程。本文将为读者介绍ThoughtWorks公司常用的一组过程工具,以及在敏捷项目中使用这些工具的些许经验,希望能帮助读者略微缓解这种痛苦。
作为一个程序员,笔者对过程工具的关注也更多地集中在技术层面上。从我的角度看来,敏捷方法最为重要、也最立竿见影的部分当属测试驱动与持续集成,我们的工具之旅就从这两件事情开始。
在敏捷开发的工具箱里,JUnit很可能是最广为人知、也最受到重视的一个,当然它也当得起这样的殊荣。若论出身,JUnit是由Kent Beck与Erich Gamma两人共同创造的。若论影响,由它引入的“红条/绿条”更是影响深远。
<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="_x0000_s1026" type="#_x0000_t75" style='position:absolute; margin-left:0;margin-top:0;width:225pt;height:210.75pt;z-index:-1' wrapcoords="-72 0 -72 21523 21600 21523 21600 0 -72 0"> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image001.png" o:title="" /> <w:wrap type="square" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]--><!--[if gte vml 1]><v:shape id="_x0000_i1025" type="#_x0000_t75" style='width:162.75pt;height:189pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image003.png" o:title="" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
<!--[if gte vml 1]><v:shape id="_x0000_i1026" type="#_x0000_t75" style='width:409.5pt;height:150pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image005.png" o:title="" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
<!--[if gte vml 1]><v:shape id="_x0000_i1027" type="#_x0000_t75" style='width:378pt;height:202.5pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image007.png" o:title="" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
<!--[if gte vml 1]><v:shape id="_x0000_i1028" type="#_x0000_t75" style='width:342pt;height:240pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image009.png" o:title="" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
图1:各种各样的Green Bars
“红条/绿条”不仅仅是一个测试成功与失败的标记,它们构成了敏捷开发中最基本的韵律:“编写测试 – 红条 – 编写代码 – 绿条 – 重构”,这五个简单的步骤确保了每一个思绪都有单元测试作为记录,每一段代码都有单元测试保证它的质量,整个软件项目始终朝着价值最大化、质量最优化的方向前进。而红条与绿条,则清晰直观地指出你当前所在的位置,以及下一步应该做的工作。一个醒目的红条/绿条对于开发者的心理有着如此重要的暗示作用,以至于当微软在Visual Studio 2005的单元测试工具中没有放置这样一个醒目的标志时,竟引来了开发者的一片怨声载道。
当我说JUnit的时候,我意指的是整个xUnit家族——覆盖了从Java/.NET直到C/C++再到Haskell/Eiffel最后到JavaScript/Ruby/Python的大家族。虽然运行在不同平台、不同语言,它们拥有同一组无法错认的特征:红条/绿条、TestCase/TestSuite……这也让开发者们无论走到哪里总可以感到放心。在开始编写你的任何一段代码之前,先为它写上一段测试;尽可能频繁地运行所有的测试。你可以立刻感受到TDD带来的帮助。
不过还有一个催化剂可以让这种化学变化来得更加强烈,那就是mock框架。以EasyMock和JMock为代表的mock框架,其作用是模拟被测对象之外的相关对象,从而实现对象之间的解耦合,达到真正意义上的“单元测试”。奇妙的是,当你把mock框架放入测试驱动实践中时,开发过程就会引导着你得到低耦合、模块化的设计,因为你很难为一组紧密耦合的类编写测试,而mock框架则让你愿意采用IoC模式分离出更多的类来承担各自的责任。在.NET和Ruby等主要平台上,也有类似的mock框架可供选择。
抛开所有的技术考量,单凭直觉来说,我个人认为Selenium最大的优点在于它的震撼性:成百上千个test case运行起来,只见web页面不断地在屏幕上闪过,模拟着各种各样的用户操作,最后生成一张庞大的报表,背景是令人平静愉悦的淡绿色。即便对于全然对于不懂技术的管理者或客户,这一过程的心理效果也是不言而喻的。
<!--[if gte vml 1]><v:shape id="_x0000_i1029" type="#_x0000_t75" style='width:6in;height:152.25pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image011.png" o:title="02" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
图2:Selenium也采用了红条/绿条的界面设计
在没有使用Selenium之前,很多人都认为web界面是无法测试驱动、也无需测试驱动的。但此时我们就常常遇到这样的困境:客户认为页面上的一句话应该这样写,开发者却认为客户三天前的意见是另一样,认真回想起来却又没人能记得三天前到底说过什么。如果说JUnit单元测试记录了开发者对于软件的设计思路,那么Selenium就记录了客户对于软件功能的要求,并时时验证客户的要求仍然得到满足。
Selenium的用法有很多种。你可以把它放在构建流程之外,由客户定期检验软件的功能是否符合需求;你也可以让它作为构建流程的一部分,用它来驱动功能的开发,形成一个更大范围的“红-绿-重构”循环。除了编写HTML格式的测试脚本之外,Selenium还支持编程驱动的模式,可以用程序代码来编写可复用的测试案例。而且整个Selenium是用JavaScript和HTML编写的,这也就意味着你可以轻松地将其融入任何一个web应用,不管开发这个应用的平台是J2EE还是.NET或者Rails。
Selenium的名字还有一个有趣的由来:在Selenium出现之前,最著名的web应用功能测试工具当属Mercury Quanlity Center,但那是一个商业工具,功能强大却也价格不菲,常常让开发者们又爱又恨。所以,自己动手开发开源功能测试工具的ThoughtWorker们把这个工具叫做Selenium——“mercury”有“水银”的意思,而“selenium”(硒元素)恰好是专解汞中毒的特效药。
现在你已经用JUnit记下了所有设计思路,也用Selenium记下了所有功能需求。你还需要让所有测试案例都能自动运行,这样才能频繁地验证所有测试仍然顺利通过。这时Ant就来到了你的手边。由Apache组织开发,又有多年的实践检验,Ant已经积累了大量实用的插件,几乎所有常用的任务都有对应的插件可以完成。而且在Ant脚本中可以直接调用Java类(.NET平台下的NAnt更是可以直接在脚本中插入C#代码),也就是说你实际上可以用Ant来做任何事。
不过最通常的用法,还是用Ant来完成整个构建流程:从编译源代码,到打包应用,到部署服务器,到初始化数据库,再到执行测试并生成测试报告,只需要一个指令就可以全部完成。类似的构建工具在C/C++的世界里早已存在,那就是著名的make,不过Ant来得更加简单易用而已。
作为后起之秀的Maven比之Ant最大的优势在于它内建了更多对J2EE项目(尤其是web项目)的支持,以及更多项目管理相关的功能。像单元测试、创建报表等在Ant中用插件实现的功能,在Maven中都有内建的支持。甚至很多开源项目的网站都是用Maven直接生成的。但以笔者愚见,Maven强大的功能更适合开源软件这样组织松散的项目,对于一般的企业应用Ant已经足够了。
不过在进入了Ruby/Rails的世界之后,笔者感觉Ant和Maven都有一个共同的缺陷:它们的构建脚本都采用XML编写,因此代码量相当巨大,并且也不易理解。相比之下,Rake直接用Ruby来编写构建脚本,反倒显得更加简洁易用。也许相比于XML,我们还需要找到另一种更适合描述“软件构建”这一任务的领域专用语言。
任 何一个多人开发的项目都必须有版本控制系统——即便一个人开发,也需要版本控制系统来提供备份和恢复的支持。由于有全面的测试作为保障,敏捷方法提倡采用 “所有人拥有所有代码”的代码所有权形式,这也就意味着时常会出现两个人同时修改一份文件的情况,在一些配置文件上这种情况出现得更加频繁。因此,敏捷开 发所使用的版本控制系统最好不要采用文件级的锁定——这正是VSS缺省的锁定方式,不过我们也可以通过配置来改变锁定方式。而开源的选择,则是CVS与Subversion(简称SVN)。
CVS与SVN存在着一些差异,不过作为开发者的我们通常不必介意这些区别。值得注意的一点是,当出现文件冲突时,SVN会强迫用户首先消解冲突,然后才能将文件提交到代码库。另外,每当有人成功提交新文件之后,SVN会为整个项目生成一个新的修订版本(revision),而不是像CVS那样为每个文件单独记录修订版本,这也让版本控制变得更加容易。
在一个敏捷项目中,每个开发pair每隔15分钟到半小时(最多不超过1小时)就会提交自己最新的代码,一个项目组每天通常会生成数十个修订版本。即便不算其中无法成功构建的版本,通常每周也能收获上百个构建版本。察看每个构建版本对应的报表,就是项目管理者有效掌握项目进度的最佳途径。
我们刚才已经提到,在开发者不断提交的修订版本中,有一部分是无法成功构建的,原因就是在版本控制系统上运行着一个忠实的看门人:持续集成工具。它们的代表就是由ThoughtWorks员工开发和维护的CruiseControl。
CruiseControl的任务非常简单:每当有人提交了新的文件到代码库,它就把整个项目签出(check-out)到一个测试环境,然后执行项目的构建脚本,完成整个构建流程,并运行所有测试。如果这一切都顺利通过,CruiseControl就会生成一个新的构建版本;否则,构建失败,所有人都不允许再签出或提交任何代码,直到造成破坏失败的人把问题解决掉,让构建重新成功为止。
一个能够成功构建的代码库代表着项目的健康。保持项目健康是如此重要,因此CruiseControl必须以最引人注目的方式告诉整个团队:我们的项目现在怎么样了。为此,各个团队想出了种种办法。我们可以用一个客户端工具来监控CruiseControl的构建情况,然后让这个客户端在发现构建失败时调用别的程序:唱一段歌,发出怪叫,或者点亮一盏灯……总之,让大家都知道。
<!--[if gte vml 1]><v:shape id="_x0000_i1030" type="#_x0000_t75" style='width:297.75pt;height:134.25pt'> <v:imagedata src="file:///C:\DOCUME~1\jxiong\LOCALS~1\Temp\msohtml1\01\clip_image013.png" o:title="" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->
图3:红灯/绿灯,构建结果一目了然
“敏捷宣言”中清楚地写着:“个体与交互胜于过程与工具”。对于一个敏捷团队来说,最重要的事情莫过于建立有效的交流渠道。在ThoughtWorks,每个项目都有自己的wiki,开发者、项目经理、客户……所有的项目涉众都会把自己的收获与心得记录在wiki上,以便大家分享和交流。
开源的wiki很容易找到,我在这里不必多说。我唯一想要介绍的,是这个叫做TiddlyWiki的小东西。这是一个完全用HTML和JavaScript编写的wiki,使用它甚至不需要服务器,你只要在本地打开这个HTML文件,就可以开始写你的wiki了。你所写的内容同样会被保存在这个HTML文件中,并且还提供了修改跟踪的能力。使用这个wiki,你可以把整个文档放到你的版本控制系统中,不需要为它做任何额外的配置。
另 外,别忘了一个最重要的工具:纸卡片——你可以随便写,用任何颜色写,可以画任何你想画的图,可以用任何顺序来排列粘贴它们,可以任意移动,可以随时加上 注释,谁都知道怎么使用它们。你甚至还可以撕掉它们,优质的纸卡片撕起来有一种特别的手感……好吧,纸卡片不是软件,通常也不免费,但你知道怎么得到它 们。
图4:ThoughtWorks——值得信赖的全球纸卡片提供商