持续集成实践之CruiseControl
hyysguyang 2006-1024
 
1. 疲于奔命得一天
       这么模块以前已经编写好得,现在怎么出问题了?
       上个礼拜编写得测试用例怎么现在通不过了?
       终于完成了一个功能了,提交代码,突然间想起昨天花了很大精力修复的那个bug,那个模块可是以前就已经完成了的,心惊胆颤,我现在完成的这个模块是否对其他模块产生影响?    哎,这些问题是什么时候出现的?我该从什么地方开始?要是在出问题的时候谁能告诉我该有多好啊?没办法,捉bug吧……OK,好了,一切都好了。
这么模块以前已经编写好得,现在怎么出问题了?
       ……
       重复,重复,再重复,疯掉了,这应该是机器做的事情,为什么……恩,编写脚本吧,哦,好像持续集成能够解决这个问题。
2. 持续集成概述
       持续集成来源于XP(极限编程)的一个实践。事实上,这个实践早就存在,并且很多并没有实际XP的也在实际着它。
2.1 持续集成的优点
       持续集成最基本的优点就是:持续集成可以减少集成阶段修复bug消耗的时间,从而最终提高生产力。  
       正如在solar的开发过程中我们所遇到的,因为某个人在工作的时候踩进了别人的领域、影响了别人的代码,而被影响的人还不知道发生了什么,于是bug就出现了。这种bug是最难查的,因为很难有人去特意关注它,随着时间的推移,问题会逐渐恶化。直到有一天,某一不小心触碰到了,然后就是花很多很多的经历去修复。嘿嘿,你现在修复了,说不定下个礼拜这个bug又冒出来了呢。
2.2 持续集成的优点
       如果使用持续集成,这样的bug绝大多数都可以在引入的同一天就被发现。而且,由于一天之中发生变动的部分并不多,所以可以很快找到出错的位置。如果找不到bug究竟在哪里,你也可以不把这些讨厌的代码集成到产品中去。所以,即使在最坏的情况下,你也只是不添加引起bug的特性而已。
       当然,持续集成的排错能力取决于测试技术,你所拥有的测试套件越是详尽,你的系统bug越少,质量越高,排错能力越强。因此,在实施持续集成 之前,你应该要有一套详尽的单元测试套件,当然,如果你采用TDD的方式进行开发那是最好不过了。
2.3 持续集成最佳实践
       持续集成的关键是自动化。绝大多数的集成都可以而且应该自动完成。读取源代码、编译、连接、测试,这些都可以自动完成。最后,你应该得到一条简单的信息,告诉你这次创建是否成功:“yes”或“no”。如果成功,本次集成到此为止;如果失败,你应该可以很简单地撤消最后一次的修改,回到前一次成功的创建。在整个创建过程中,完全不需要你动脑子。在solar中,我们采用邮件通知的方式。Martin Fowler 在其文章中提到,持续集成应该:
²        单一代码源
²        自动化创建脚本
²        自测试的代码
²        主创建
²        频繁的提交(Check in)代码
       更详细的内容请参考Martin Fowler的文章 Continuous Integration
2.4 持续集成与日构建
           
       持续集成不是日构建,与日构建建相比它还有以下特点:
       1. 持续集成强调了集成频率,和日创建相比,持续集成显得更加频繁。
       2. 持续集成强调及时反馈,日创建的目的是得到一个可以使用的稳定的发布版本,而持续集成强调的是集成失败之后向开发人员提供快速的反馈,当 然成功创建的结果也是得到稳定的版本。
       3. 日创建并没有强调开发人员提交(check in)源码的频率,而持续集成鼓励并支持开发人员尽快的提交对源码的修改并得到尽快的反馈。
             
从上面明显看出,“ 频率”和“反馈”这两个词出现的特别多,持 续集成有一个基本要点,那 就是“ 经常性的集成比偶尔集成要好”。Martin Fowler 认为对于持续集成来说,集成越频繁,效果越好 ,如果你的集成不是经常进行的(少于每天一次),那么集成就是一件痛苦的事情,如果集成偶尔才进行一次(一周甚至一个月), 等到集成阶段发现bug,然后找原因解决bug,会耗费你大量的时间与精力,而且这种方式有点象传统的集成模式,这违背了持续集成的初衷.
3. Solar 持续集成环境
       Solar的持续集成环境基于CruiseControl搭建。 CruiseControl 原属于thoughtworks公司,后来开放源码,它是一个流行的功能强大的持续集成框架,包括了邮件通知,ant 和各种源码控制工具的插件。并提供了 web 接口,用于查看当前和以前的创建的结果。在下文我们采用cc代替Cruise Control。
4. CruiseControl环境搭键
4.1 Build Loop
       前面在讨论持续集成的时候讲到其最重要的特征之一是自动化,而 CC 的 Build Loop 就是为支持自动化而设计的,Build Loop 也是 CC 的核心。CC 提供了一个 daemon 进程,该进程自动根据配置的时间间隔或指定的某个具体时间读取 CC 配置文件并进行循环创建,每次 CC 都会重新加载配置文件,在CC运行过程中,修改了配置文件不用重新启动 CC。
       Build Loop 过程中所做的工作如下:访问源码源码控制系统,查看是否有代码被修改,如果有,获取源码的新版本,并根据配置对源码进行一次 Build,创建一个日志文件,最后向开发人员通知 build 的结果,活动图如下:
图1. Build Loop 状态图
4.2 CC的配置文件
       cc最主要的配置文件是一个叫做config.xml的配置文件,当然你也可以取其他名字,只要运行cc的时候指定输入文件就行了,类似于ant命令一样。以下是一个示例文件的内容。
config.xml
  1. xml version="1.0" encoding="GBK"?>  
  2.   
  3.   
  4. <cruisecontrol>  
  5.    
  6. <property environment="env"/>  
  7.     <property name="ant.home" value="${env.ANT_HOME}"/>  
  8.     <property name="product.name" value="numen"/>  
  9.   
  10.     
  11.   <project name="solar-CI-build" buildafterfailed="false">  
  12.   
  13.     <dateformat format="yyyy-MM-dd HH:mm:ss"/>  
  14.     <listeners>  
  15.       <currentbuildstatuslistener file="logs/${project.name}/currentbuildstatus.txt"/>  
  16.     listeners>  
  17.   
  18.     <bootstrappers>  
  19.       <currentbuildstatusbootstrapper  
  20.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  21.     bootstrappers>  
  22.   
  23.     <modificationset ignoreFiles="*.css,*.js,*.jsp">  
  24.       <svn localworkingcopy="checkout/${product.name}"/>  
  25.     modificationset>  
  26.   
  27.     <schedule interval="7200">  
  28.        
  29.       
  30.     <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  31.            target="run.command.tests" multiple="3"/>  
  32.               
  33.              
  34.       <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  35.            target="run.plain.tests"  multiple="1"/>  
  36.               
  37.     schedule>  
  38.   
  39.     <log dir="logs/${project.name}">  
  40.       <merge dir="checkout/${product.name}/test/report/xml"/>  
  41.     log>  
  42.   
  43.     <publishers>  
  44.       <currentbuildstatuspublisher  
  45.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  46.   
  47.       <artifactspublisher dir="checkout/${product.name}/build"  
  48.                           dest="artifacts/${project.name}"/>  
  49.   
  50.       <htmlemail mailhost="mail.cenosoft.cn"  
  51.                  returnaddress="tech@cenosoft.cn"  
  52.                  defaultsuffix="@cenosoft.cn"  
  53.                  buildresultsurl="http://192.168.7.236:10000/buildresults/${project.name}"  
  54.                  css="report/css/cruisecontrol.css"  
  55.                  xsldir="report/xsl"  
  56.                  logdir="logs/${project.name}"  
  57.                  charset="GBK">  
  58.         <map alias="guyang" address="hyysguyang@gmail.com"/>  
  59.         <failure address="guyang" reportWhenFixed="true"/>  
  60.       htmlemail>  
  61.             <XSLTLogPublisher directory="WebRoot/rss" outfilename="numenbuildstatus.rss" xsltfile="report/rss/xsl/buildstatus.xsl" />  
  62.     publishers>  
  63.   project>  
  64.   
  65.     
  66.   <project name="solar-nightly-build" buildafterfailed="false">  
  67.        
  68.     <dateformat format="yyyy-MM-dd HH:mm:ss"/>  
  69.     <listeners>  
  70.       <currentbuildstatuslistener file="logs/${project.name}/currentbuildstatus.txt"/>  
  71.     listeners>  
  72.   
  73.     <bootstrappers>  
  74.       <currentbuildstatusbootstrapper  
  75.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  76.     bootstrappers>  
  77.   
  78.     <modificationset requiremodification="false">  
  79.       <svn localworkingcopy="checkout/${product.name}"/>  
  80.     modificationset>  
  81.   
  82.     <schedule>  
  83.       <ant anthome="${ant.home}" buildfile="cc-build.xml"  
  84.            target="run.all"  
  85.            time="0300"/>  
  86.     schedule>  
  87.   
  88.     <log dir="logs/${project.name}">  
  89.       <merge dir="checkout/${product.name}/test/report/xml"/>  
  90.     log>  
  91.   
  92.     <publishers>  
  93.       <currentbuildstatuspublisher  
  94.           file="logs/${project.name}/currentbuildstatus.txt"/>  
  95.   
  96.       <artifactspublisher dir="checkout/${product.name}/build"  
  97.                           dest="artifacts/${project.name}"/>  
  98.   
  99.       <htmlemail mailhost="mail.cenosoft.cn"  
  100.                  returnaddress="tech@cenosoft.cn"  
  101.                  defaultsuffix="@cenosoft.cn"  
  102.                  buildresultsurl="http://localhost:10000/buildresults/${project.name}"  
  103.                  css="report/css/cruisecontrol.css"  
  104.                  xsldir="report/xsl"  
  105.                  logdir="logs/${project.name}"  
  106.                  charset="GBK">  
  107.         <map alias="guyang" address="hyysguyang@gmail.com"/>  
  108.   
  109.         <failure address="guyang" reportWhenFixed="true"/>  
  110.       htmlemail>  
  111.             <XSLTLogPublisher directory="WebRoot/rss" outfilename="numenbuildstatus.rss" xsltfile="report/rss/xsl/buildstatus.xsl" />  
  112.   
  113.     publishers>  
  114.   
  115.   project>  
  116.   
  117. cruisecontrol>  
4.3 配置示例
Ø         下载CC
Ø         Build CC
Ø         建立build结构
Ø         编写config.xml
Ø         引入wrapper buildfile
Ø         Report Result
4.3.1下载CC
     你可以从下面的连接中下载cc的源码包或者binary包。
       CC Download
       Source Distribution
       Binary Distribution
       解压缩到某一个目录,如D:\cruisecontrol-2.5,以下我们用 CC_HOME 来引用这个目录,将%CC_HOME%\main\bin添加到系统的%PATH%环境变量中去。
4.3.2 Build CC
    %CC_HOME%目录下的结构如下:
 
图2. CC目录结构
4.3.3创建 cruisecontrol.jar
       运行 %CC_HOME%\main\build.bat , 该命令会编译 , 测试 , 并创建%CC_HOME%\main\dist\cruisecontrol.jar。现在即可通过以下方式运行 %CC_HOME%\main\bin\cruisecontrol.bat ,
%CC_HOME%\main\dist\java jar cruisecontrol.jar
或者在命令行输入cruisecontrol命令。
4.3.4创建 cruisecontrol.war
       在创建 cruisecontrol.war 之前,我们需要传递三个参数给创建程序,分别用来指定 CC 产生
       的日志的目录(user.log.dir),创建过程中产生的状态文件的名字(user.build.status.file),还有创建过程中输出的人工制品所在的目录(cruise.build.artifacts.dir)。假如我们的build home为/home,则其对应的值如下:
user.log.dir=/home/logs
user.build.status.file=currentbuildstatus.txt
cruise.build.artifacts.dir=/home/artifacts
转到%CC_HOME%\reporting\jsp\,然后运行以下命令:
              build -Duser.log.dir=/home/logs
                     -Duser.build.status.file=status.txt
              -Dcruise.build.artifacts.dir=/home/artifacts
       当然你可以不指定这三个参数,仅仅运行build,然后会提示你输入这三个参数的。
       另外一种方法,就是在该目录下创建一个新文件 override.properties,文件内容如下,在改文件中指定这三个属性,然后build,就可以了,如。user.log.dir=/home/logs
                     user.build.status.file=status.txt
                     cruise.build.artifacts.dir=/home/artifacts
4.3.5建立build结构
 
 
图3. CC Build结构
       artifacts 目录:存放CC 在 build loop 过程中产生的人工制品,如JAR,对于我们的solar就是每一次集成build的solar.jar,solar-test.jar
       checkout 目录:作为 CC 从源码控制系统检出
的项目存放的目录,对于我们的solar就是从svn上检出存放的目录,当然这里可以存放多个项目。
       Logs 目录:CC 的 build loop 过程中产生日志所在的目录
4.3.6编写config.xml
参见上文的config.xml文件。下面解释各个节点,更详细的内容请查看官方文档。
:设置项目状态文件
quietperiod属性:开始build时间与实际build时间的间隔
ignoreFiles:在svn变化时忽略变化的文件类型
ignoreFiles="*.css,*.js,*.xsp,*.xal“
调度build
合并日志的
合并日志
:发布build结果
4.3.7引入wrapper buildfile
       由于我们是基于ant进行构建的,当build文件变化时,我们应该先要update build.xml,你可以在bootstrappers的时候update这个文件当然,你也可以采用类似的方式update类似的文件。
   
           file=“checkout/solar/build.xml" />
此外,还有一种替代方法是使用 wrapper buildfile,这样就不用使用了。对应上文的config.xml文件的wrapper buildfile如下:
cc-build.xml
  1. xml version="1.0" encoding="GBK"?>  
  2.   
  3. <project name="cc-build" default="build" basedir=".">  
  4.   <property file="cc-build.properties"/>  
  5.   <taskdef resource="svntask.properties"/>  
  6.   
  7.   <target name="update.resource">  
  8.     <svn javahl="false">  
  9.       <update dir="${numen_base_dir}" revision="head"/>  
  10.     svn>  
  11.   target>  
  12.   
  13.   <target name="build" depends="update.resource">  
  14.     <ant antfile="build.xml" dir="${numen_base_dir}" target="clean"/>  
  15.     <ant antfile="build.xml" dir="${numen_base_dir}" target="build"/>  
  16.   target>  
  17.   
  18.   <target name="run.plain.tests" depends="update.resource">  
  19.     <ant antfile="build.xml" dir="${numen_base_dir}" target="clean"/>  
  20.     <ant antfile="build.xml" dir="${numen_base_dir}" target="run.plain.tests"/>  
  21.   target>  
  22.   
  23.   <target name="run.command.tests" depends="update.resource">  
  24.     <ant antfile="build.xml" dir="${numen_base_dir}" target="clean"/>  
  25.     <ant antfile="build.xml" dir="${numen_base_dir}"  
  26.          target="run.portal.command.tests"/>  
  27.     <ant antfile="build.xml" dir="${numen_base_dir}"  
  28.          target="run.admin.command.tests"/>  
  29.   target>  
  30.   
  31.   <target name="run.all" depends="update.resource">  
  32.     <ant antfile="build.xml" dir="${numen_base_dir}" target="clean"/>  
  33.     <ant antfile="build.xml" dir="${numen_base_dir}" target="clover.html"/>  
  34.     <copy todir="${webapp_root_dir}/clover">  
  35.       <fileset dir="${solar_base_dir}/test/clover/html" includes="**"/>  
  36.     copy>  
  37.   target>  
  38.   
  39. project>  
 
4.3.8 Report Result
       要发布构建结果很简单,只要cruisecontrol.war放到任意一个web 容器中即可浏览cc的构建结果。
5. 小结
持续集成有很多好处,可以大量降低集成时间,此外更最重要的是它可以迅速反馈,给我们勇气和信心,当然这个是建立在详尽的单元测试的基础上的。显然持续集成已经不像以前那样只是挂在嘴巴的高深的名词,只要你愿意你都可以实施持续集成,然而你还是需要遵循某些原则,遵循这些原则比持续集本身更重要。
       1. 频繁的commit.
       2. 确保编译通过,确保所有测试都通过。
       3. 及时反馈,让前一次构建发现的bug不要在下一次重复出现。如果你是一个优秀的开发人员,相信你不会让某个bug驻留太久。
       4. 测试你的代码,建立详尽的单元测试suite,
   总之,持续集成仅仅是我们项目过程中的一个工具而已,正如scm工具、单元测试一样。     工具就是工具,人比什么都重要,正如XP推崇的。
6. 参考文献
评论
hyysguyang 2007-02-25
是这样的,在一个完整的构建之中,可能会包括很多步骤,比如编译,运行测试,生产javadoc文档,打包发布等等。只要任何一个步骤出错了,CC都认为构建失败。实际上我是采用ant构建的,因此可以之么说,只要ant构建失败,CC也就意味着构建失败了.
wurenjian 2007-02-06
只要编译成功,CC都认为是构建成功;对于编译成功测试失败它也认为是构建成功,怎么设置测试失败发送邮件?
发表评论

您还没有登录,请登录后发表评论

hyysguyang
搜索本博客
最近加入圈子
存档
最新评论