一、什么是持续集成(Continuous Integration)?
这个名词已经在软件开发领域持续了N年,一个比较简单的定义如下:
持续集成(CI)是一种实践,可以让团队在持续的基础 上收到反馈并进行改进,不必等到开发周期后期才寻找和修复缺陷。 通俗一点儿说,就是指对于开发人员的每一次代码提交,都自动地把Repository中所有代码Check out到一个空目录,并且自动运行所有Test Case。如果成功则接受这次提交,否则告诉所有人,这是一个失败的Revision。 更具体的解释可以参考Martin fowler 的Continuous Integration 。
二、持续集成的价值与成本
有句时髦的话,叫做“存在即为合理”。既然持续集成已经存在了这么长的时间,而且没有消失的迹象,那就是有价值的东西。那么它的价值何在?有人概括如下 :(1) 减小风险;(2) 减少手动过程;(3) 生成构建结果;(4) 安全感。
而持续集成的成本在于对持续集成代码的维护成本和集成的时间成本。因为随着项目进行,软硬件环境会越来越复杂,成品代码也会不断膨胀。此时,需要团队而修改或增加原有的测试代码,以适应这些变化,同时,每次集成所需时间也会变长,这就是持续集成的成本。某个blog中提道 :“这种集成是如此的频繁,多少次的代码Commit就有多少次持续集成。前提是集成的成本很低,或者说是完全自动化的。”
三、持续集成应该自动化什么呢?
我们要以尽可能少的成本来获得尽可能多的价值。这就要考虑哪些自动化是必要的啦。Jez Humble提到至少有六点要做到自动化 ,它们分别是(1)自动化的运行测试;(2) 自动产生可部署的二进制成品;(3) 自动将成品自动部署到近似生产环境;(4) 自动为CodeBase打上标签;(5) 自动运行回归测试;(6)自动生成度量报告。
四、持续集成服务器的选择
在 进行持续集成实践前,应当正确的选择并配置持续集成服务器。比较成熟的持续集成服务器包括:CruiseControl, Anthill, Bamboo, TeamCity, Continuum 等。CruiseControl作为开源产品,以其对于各种SCM以及构建工具的广泛支持而被许多开发团队所接受。而开发自动化专家 Duvall 采用一致的评估标准和很多说明性示例,介绍了一些开源 CI 服务器,包括 Continuum、CruiseControl 和 Luntbuild。并指出“ 要根据 自己的 具体技术和政策需求对工具进行分析 ” 。并用以下五个指标来评估CI工具,它们分别是:(1) 特性;(2) 可靠性;(3) 寿命;(4) 目标环境;(5) 易用性。结果如下表:
而CruiseControl是我唯一真正用过的持续集成工具,它现在灵活而又强大功能也让我瞠目,而且配置与管理也较两年前容易得多啦。 为什么说它强大呢?因为你只要想得到的问题,它也都会有所考虑。朋友的Blog 上有些CruiseControl的最佳实践足以证明这一点,只要你肯去实践。
五、只有持续集成服务器是远远不够的
正如Jez Humble所说,CruiseControl和其它的CI工具本质上只不过是一个定时器,时间一到,做你让它做的事情 。 所以,必然要有其它工具与其结合,方显持续集成的本色。这些工具又是什么呢?想测试的话,你就要用一些测试工具,如 JUnit,JWebUnit,Selenium等等;想检查代码标准的话,你就要用checkstyle等代码规范检查工具;想要了解测试覆盖率的话, 你可能就要用到JCoverage啦。当然,想得到二进制文件,就要用到Ant,Make之类的工具啦。
六、最重要的事:实践与反思
也 许这些东西大家都知道,而且有些人可能已经实践过啦。无论这些实践的结果是怎样的,一定不要忘记总结和反思。如果这些实践成功了,不要把它归功于这个工 具,而是要总结一下为什么会成功,如果你愿意的话,还可以和大家分享一下。如果这些实践失败了,也不要把它归功于这个工具,而是要反思一下,是否正确地使 用了这个工具,团队成员是否都喜欢这个工具,为什么?
CruiseControl是CI服务器的老者,诞生已是多年,在许多方面,CruiseControl服务器已经成为持续集成实践的同义词。而现 在,CruiseControl已发展成为一个家族式系统,包括CruiseControl.java、CruiseControl.net、 CruiseControl.ruby等适应不同语言环境的实现,其强大的插件和扩展能力也是诸多同类系统无法比你的。而在这里,我只介绍该家族的本家 CruiseControl.java,即CruiseControl。CruiseControl是一个不错的持续集成服务器,不过国内社区的普遍反应 是:它的配置太麻烦,无从下手。从本篇文章开始,我将逐步介绍CruiseControl的环境搭建与配置,以及持续集成中的一些实践。我们将从最简单的 使用方式(stand-alone)开始,以便您可以快速开始您的持续集成之旅,而不必面对“千奇百怪”的问题。
好!先让我们简单搭建个持续集成服务器再说吧
一、我使用的CruiseControl版本
- CruiseControl是一个java开源项目,我将以它最近的一个分支版本 来介绍它的使用。CruiseControl最近发布的版本是V2.8.1。这个分支版本就是在V2.8.1基础之上。
- 这个分支版本包含一个Dashboard,可以方便地看到各项目的构建状态。
- 这个分支版本当然还支持多个Build Loop,你可以在多个机器上部署独立的Build Loop,从而建立你自己的Build Grid。
二、得到CruiseControl的分支版本
要得到这个分支版本,你可以使用subversion不用任何密码从CruiseControl的Repository中检出。命令如下:
svn checkout https://cruisecontrol.svn.sourceforge.net/svnroot/cruisecontrol/branches/cce/cruisecontrol/
三、构建CruiseControl
0. 你机器上至少要安装有JAVA 1.4,并在环境变量中设置有JAVA_HOME,并把java/bin加入到path中。CruiseControl自带了ANT,所以不用事先安装。
- 从源代码中构建CruiseControl
如果从源码中构建CruiseControl,要确保你的机器可以访问互联网。因为构建过程中要检查一些license。另外,最好有SVN 客户端软件,因为在构建时,它会用到javahl。
在刚刚checkout出来的CruiseControl目录下,有一个批处理文件,名为release.bat。运行这个批处理文件,你会在 target目录下发现一个压缩文件,名这cruisecontrol-bin-2.7.2-dev.zip。将其解压到一个目录,我们这里解压到C盘根 目录下。解压后,目录结构如下:
2. 直接使用已构建完成的CruiseControl
如果你不想做这一步,也可以在binaryrelease目录下发现解压后的文件,把它们拷贝到相应的目录下也可以。
四、运行CruiseControl
CruiseControl可以运行在多种应用服务器中,当然,它自己也捆绑了一个开源应用服务器,那就是Jetty 6。为了尽快建立我们的环境,我们这里暂时使用它自己捆绑的Jetty,快速开始我们的持续集成之旅。
直接运行CruiseControl目录下的CruiseControl.bat,它会自行启动Jetty6,当从Console中看到它启动完毕后,你 就可以在浏览器中访问http://localhost:8080/dashboard,此时你会看到一个名为connectFour的示例项目在 Dashboard上显示了。
(如果你足够快的话,你会发现Dashboard 上的小方块是从灰色变成深绿色的。为什么呢?我们以后再说。)
“绿色”表示该项目构建通过。如果要是有多个项目的话,应该看到多个小方块。
点击“Builds” Tab页,就到了 Builds 页。这一页会以列表的方式显示所有的项目。当前只有一个项目,所以在列表中只有一行,如下图所示:
在该页面上点击某个项目时,会打开Build Detail 页, 显示该项目最近一次Build的信息。页面右侧是该项目所有历史构建信息。你会发现,这时connectFour项目有两个构建历史版本,一个是刚刚构建 完成的,另一个是两年前的构建。这些信息是来自于CruiseControl对该项目的日志文件。此时,点击这两个构建结果中的任何一个,都会打开该次构 建的Build Detail 页面,显示相关的构建历史信息。如下图所示:
我们将在下一篇文章中简要介绍CruiseControl的配置文件,为您加入自己的构建项目做准备。
上一篇文章中,我们已经看到CruiseControl开始工作了。接下来我们先了解一个它的配置文件,然后再加入你的项目。
CruiseControl的配置文件的确了比较复杂,一方面是因为:它太灵活,太想完成你想达到的任何效果了。另一方面就是:它在文档化方面的确很落 后。你只能在用户组中找答案。(不过,这也是学习的一个途径。如果你了解开源的话,你就会习惯于在用户组中找答案啦。)
再了解配置文件之前,让我们先定义一下我们可能会用到的术语。
- 工作目录 (也就是CruiseControl的工作目录):是指启动CruiseControl的脚本目录。在Stand-alone情况下,就是文件cruisecontrol.bat所在的目录。如果你把它部署到了应用服务器中,那一般就指bin目录了。
2.
Log 目录
:就是CruiseControl存储所有构建项目日志信息的目录,默认值为工作目录下名为logs目录。
3.
Projects目录:
就是CruiseControl存储所有构建项目源代码的目录,默认值为工作目录下名为projects目录。
一、配置文件及其正确位置
配置文件包括两个,一个名为
config.xml
,另一个名为
dashboard-config.xml
。默认情况下,这两个文件和cruisecontrol.bat在同一下目录下,即
工作目录
。
如果在配置文件中,任何路径前面如果没有“/”的话,那就是相对于
工作目录
的。
另外,CruiseControl是可以改变他们的位置的,但建议在没有深入了解CruiseControl时,不要试图改变它们,因为我们的目标是让CruiseControl在最短的时间里为你创建价值。
二、Config.xml 文件
< project name ="connectfour" > --这里的name应该和你在projects目录下的项目名完全一致。
< listeners > --用于监听项目状态的变化(指building,passed等)
< currentbuildstatuslistener file ="logs/${project.name}/status.txt" /> --这里的${project.name}
</ listeners > 是指“connectfour”
< bootstrappers > --用于CruiseControl从Repository更新代码
< svnbootstrapper localWorkingCopy ="projects/${project.name}" />
</ bootstrappers >
< modificationset quietperiod ="30" > --用于监听在quietperiod 秒内 , Repository 是否变化
< svn localWorkingCopy ="projects/${project.name}" />
</ modificationset >
< schedule interval ="300" > --用于每隔 interval 秒, CruiseControl去检查并计划一次构建
< ant anthome ="apache-ant-1.7.0" buildfile ="projects/${project.name}/build.xml" />
</ schedule >
< log > --用于得到并保存log文件。 默认情况下,将放在projects/${project.name}的目录下
< merge dir ="projects/${project.name}/target/test-results" /> --用于将Build中生成的结果合并
</ log > 到log文件中
< publishers > --用于将构建的结果(如二进制文件)发布到指定的位置
< onsuccess > --决定在构建成功的情况下,发布哪些内容
< artifactspublisher dest ="artifacts/${project.name}" file ="projects/${project.name}/target/${project.name}.jar" />
</ onsuccess >
</ publishers >
</ project >
</ cruisecontrol >
三、dashboard-config.xml
<?
xml version="1.0" encoding="UTF-8"
?>
<!--
/********************************************************************************
这里省略了一些声明 :)
********************************************************************************/
-->
<
dashboard
>
<
buildloop
logsdir
=""
--这里指定logs目录,一定要与config.xml文件中一致,否则你会有麻烦。如果为空串,则默认为工作目录 下的logs目录。
artifactsdir
=""
/>
--这里指定
artifacts
目录,一定要与config.xml文件中一致如果为空串,则默认为工作目录 下的artifacts目录。
<
features
allowforcebuild
=""
/> --是否可以在Dashboard上强制构建(force build)
<
trackingtool
projectname
=""
baseurl
=""
keywords
=""
/> --这是用于与Mingle的集成
<
subtabs
> --在这里,你可以自己扩展你自己的subtab页,不过要先实现一些接口。
<
subtab
class
="net.sourceforge.cruisecontrol.dashboard.widgets.ErrorsAndWarningsMessagesWidget"
/>
</
subtabs
>
</
dashboard
>
四、配置文件中的注意事项
1、如果logs、artifacts和projects目录不存在,在你运行CruiseControl之前,要手工建立它们。
2、确保两个配置文件中的logs/artifacts目录指向同一个目录。因为CruiseControl core会向其中写数据,而Dashboard会从中读数据。
五、小贴士
在Config.xml文件中,有几个元素的概念要先澄清一下(只针对当前示例,严格定义请参见CruiseControl的文档)。
- <listeners> 用于监听状态变化,如waiting for build,queued,building。一般不用改变。
- <bootstrappers>用于检出代码,CruiseControl有很多种bootstrapper,示例中使用的是SVN。localWorkingCopy 属性是指你把代码检出到安装CruiseControl机器的哪个文件目录。
- <modificationset>用于监听Repository的变化频率,如果CruiseControl发现代码有变化,而且在 quietperiod 秒内没有其它用户检入代码(用于保证被构建的版本完整性),CruiseControl才会检出代码。
- <schedule >用于指定CruiseControl去检查Repository的时间间隔。并指定使用哪个构建文件。示例中使用的是ant脚本,构建文件是被检出项目的根目录下的build.xml(即在Repository中)。
- <log>用于指定CruiseControl把日志放在哪里,把哪些构建结果放在日志中。如果没有指定任何属性,默认为logs目录下的${project.name }目录。
- <publishers > 用于在构建结束后,发布哪些消息和文件。CruiseControl有很多插件可以做各种各样的Publishing。例如,在构建成功后,通过http调用指定的页面,也可以给指定的人发邮件。在失败时,播放音乐,通知team。
好,现在我们已经在自己的机器上建立了一个持续集成服务器,并初步了解它的配置文件。在本文中,我们就要把自己的项目加到其中。 做为一个入门示例,我们先要陈述一下假设条件,以便我们快速推进我们的部署过程。
一、 准备工作(请确认一下)
1、你的项目代码放在Google Code上面(Google Code用的是SVN ),你的项目名称是yourprojectname,项目的源文件就放在trunk目录下。那么,其svn update的链接如下:http://yourprojectname .googlecode.com/svn/trunk/ 。CruiseControl只会更新文件,不会修改源代码,所以匿名检出就可以了。
2、 你的项目是一个JAVA项目,用ANT进行构建,构建文件 名为build.xml,放在项目的根目录上,默认的ant task名为all。确保文件中其中所有路径都是相对于项目根目录的,将其Copy到任何目录下,ant all都可以正确运行。(我们的主要目的是建立环境,使其在今后的开发工作中可以发挥作用,而不是要发现我们构建脚本中错误。)
3、你的CruiseControl工作目录是C:/CruiseControl,方便起见,我们以下将用${CC_HOME}代替。
4、你的logs目录是${CC_HOME}/logs。如果你没有独自改动配置的话,它的位置就如前所述。
5、你的projects目录是${CC_HOME}/projects。如果你没有独自改动配置的话,它的位置就如前所述。
6、你的artifacts目录是${CC_HOME}/artifacts。如果你没有独自改动配置的话,它的位置就如前所述。
二、加入项目
1、手工检出文件:
确保将你的项目代码检出到${CC_HOME}/projects/yourprojectname目录下,即在该目录就是你项目的根目录,可以找到名为build.xml文件。
2、修改配置文件(添加你的项目)
将下面的代码段加入到config.xml文件中,位于<CruiseControl>节点下。
<
project
name
="yourprojectname"
>
--这里一定要与你的projects目录下的项目名相同
<
listeners
>
<
currentbuildstatuslistener
file
="logs/${project.name}/status.txt"
/>
</
listeners
>
<
bootstrappers
>
<
svnbootstrapper
localWorkingCopy
="projects/${project.name}"
/>
</
bootstrappers
>
<
modificationset
quietperiod
="30"
>
<
svn
localWorkingCopy
="projects/${project.name}"
/>
</
modificationset
>
<
schedule
interval
="300"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
</
schedule
>
<
log
>
<
merge
dir
="projects/${project.name}/target/test-results"
/><!-- 在上面一句,一定要确保构建失败后可以找到这个dir,如果没有,删除这个元素好了-->
</
log
>
<
publishers
>
<
onsuccess
>
<
artifactspublisher
dest
="artifacts/${project.name}"
file
="projects/${project.name}/target/${project.name}.jar"
/><!-- 在上面一句,一定要确保你打包出来的文件名是yourprojectname.jar -->
</
onsuccess
>
</
publishers
>
</
project
>
最终的文件如下:
<
cruisecontrol
>
<
project
name
="connectfour"
>
<
listeners
>
<
currentbuildstatuslistener
file
="logs/${project.name}/status.txt"
/>
</
listeners
>
<
bootstrappers
>
<
svnbootstrapper
localWorkingCopy
="projects/${project.name}"
/>
</
bootstrappers
>
<
modificationset
quietperiod
="30"
>
<
svn
localWorkingCopy
="projects/${project.name}"
/>
</
modificationset
>
<
schedule
interval
="300"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
</
schedule
>
<
log
>
<
merge
dir
="projects/${project.name}/target/test-results"
/>
</
log
>
<
publishers
>
<
onsuccess
>
<
artifactspublisher
dest
="artifacts/${project.name}"
file
="projects/${project.name}/target/${project.name}.jar"
/>
</
onsuccess
>
</
publishers
>
</
project
>
<
project
name
="yourprojectname"
>
<
listeners
>
<
currentbuildstatuslistener
file
="logs/${project.name}/status.txt"
/>
</
listeners
>
<
bootstrappers
>
<
svnbootstrapper
localWorkingCopy
="projects/${project.name}"
/>
</
bootstrappers
>
<
modificationset
quietperiod
="30"
>
<
svn
localWorkingCopy
="projects/${project.name}"
/>
</
modificationset
>
<
schedule
interval
="300"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
</
schedule
>
<
log
>
<
merge
dir
="projects/${project.name}/target/test-results"
/>
</
log
>
<
publishers
>
<
onsuccess
>
<
artifactspublisher
dest
="artifacts/${project.name}"
file
="projects/${project.name}/target/${project.name}.jar"
/>
</
onsuccess
>
</
publishers
>
</
project
>
</
cruisecontrol
>
三、看一下结果
(1) CruiseControl会自动更新这个配置文件,加载时间一般是在任何一个项目需要检查是否有更新时。
(2) 你会看到这个项目在Dashboard上首先是灰色的方块,因为这个项目还从来没有在CruiseControl上构建过,没有任何历史信息,我们称该项目为"inactive"。
(3) 过一会儿,你会看到一个环形动态图标,表示它在构建中(执行build.xml脚本)。
(4) 当环形动态图标消失时,你会看到它变成红色(如果构建失败的话)或绿色(如果构建成功的话)。
我新加的项目名是vcdstore,现在我的dashboard上有两个项目了。

小贴士:
(1) 目前CruiseControl还不支持首次自动检出代码,所以你要先把项目代码手工检出到本地。
(2) 如果你的SVN使用https方式更新代码,你一定要在命令行方式检出代码,并选择永久接受证书。
(3) CruiseControl这个分支版本不支持在UI上增加项目。
(4) 如果你在config.xml文件中把你的项目删除,却不删除它的日志的话,你在Dashboard上还是可以看到这个项目,此时它被标记为"Discontinued",表示你可以看到它的历史信息,但CruiseControl不会再构建它了。
下一篇我们将讲述CruiseControl的一些界面信息元素。
现在,我们已经为自己的项目建立了持续集成服务器。让我们来看一下CruiseControl正在对你说些什么。
一、项目的状态
首先CruiseControl把项目分成三类,Discontinued,Inactive,及Active。
如 果一个项目是Discontinued,表示CruiseControl可以找到该项目的日志文件,但在配置文件(Config.xml)中并没 有该文件。所以CruiseControl不会去构建它,但你可以看到这个项目过去构建的历史信息。如果想把这个项目从CruiseControl中删 除,只有把该项目的日志目录删除才行。如果该项目名为"vcdstore",目录当该是${CruiseContor.Home}/logs /vcdstore。
如果一个项目是Inactive,表示CruiseControl在配置文件 (Config.xml)中发现了该项目,但是没有发现关于这个项目的 任何历史信息,即在CruiseControl的日志目录中还没有该项目的日志文件,或日志文件被人为删除了。CruiseControl会根据配置信息 对这个项目进行检查新版本并进行构建。当第一次构建完成后,CruiseControl就会生成日志,这个项目状态就会转为Active了。
如果一个项目是Active,表示CruiseControl即可以找到该项目的日志文件,又在配置文件(Config.xml)中可以发现它。此时,这个项目可能是构建成功,也可能是构建失败,还可能是构建中。
二、Dashboard
你可以通过 http://localhost:8080/dashboard 访问Dashboard。
Dashboard主要有四个页面,它们分别是Dashboard,Builds,Build Details和Administatiorn。
(1) Dashboard
你可以在Dashboard上看到所有项目的状态,
红色方块
表示该项目最近一次构建是失败的。
绿色方块表示该项目最近一次构建是成功的。灰色方块表示该项目 可能是Inactive的,也可能是Discontinued。
当把鼠标放在小方块上时,会显示该项目的主要信息。点击小方块,会进入Build Details页面。
(2) Builds
你可以在Builds页面上以列表的方式看到所有项目的状态,点击每个列表,可以进行Build Details页面。
如果你将ForceBuild配置为true,在列表右侧有一个按钮,你可以强迫该项目进行构建,而不必等到其下一次检查,也不必等到它有版本变化。
(3) Build Details
此页面会列出该项目某次构建的详细信息,包括与上次构建相比有哪些变化,测试结果是什么,详细的日志输出,如果构建成功的话,在配置文件(config.xml)中配置的Artifacts也会在名为Artifiacts的tab页上看到。
右侧的列表是该项目所有的构建列表,点击其中一个构建,你就可以得到该次构建的详细信息。
(4) Administration
该页面你可以看到About和Configuration两个子标签。
在About子标签中,你可以看到CruiseControl所用的环境信息,如CruiseControl的版本号、所用的操作系统和JDK版本等。
在Administration子标签中,你可以看到CruiseControl的Dashboard-config.xml文件内容。在该版本中,还不支持修改,也移除了"Add Project"按钮。
三、CruiseControl Reporting
你也可以通过链接http://localhost:8080/ 来访问CruiseControl的Old Reporting。
点击项目名称,可以看到详细内容。
四、CruiseControl JMX控制台
你也可以通过链接http://localhost:8000/ 来访问CruiseControl的JMX控制台。
在控制台上点击项目名称(如connectfour),可以修改项目配置,暂停/恢复项目构建等。
小贴士:
(1) 以上的链接均以不修改CruiseControl默认配置为基础。
(2) 后面的文章中,我们会介绍如何通过Build Grid来推展我们的CruiseControl构建能力。
如果您一直跟着这个Thread,那么现在你应该已经可以使用CruiseControl来进行持续集成了。如果你有很多项目需要持续集成的话,可 能在同一时刻会有很多项目排队等待构建的现象,以至于使各项目团队无法得到及时的反馈,此时一个集成服务器就不够了。下面我们就来扩展我们的Build Grid吧。
一、前提条件与假设
根据前面的介绍,你的第一台持续集成服务器已经可以正常运行了。这里列出如下假设,以方便后续描述。
(1) 首台持续集成服务器IP地址为:192.168.1.6,hostname为CI_One。
(2) 在首台持续集成服务器上:
CruiseControl的根目录是:C:/CruiseControl,今后用${CC.HOME}表示。
CruiseControl的Projects目录是:C:/CruiseControl/Proects,今后用${CC.Projects}表示。
CruiseControl的Logs目录是:C:/CruiseControl/Logs,今后用${CC.Logs}表示。
CruiseControl的Artifacts目录是:C:/CruiseControl/Artifacts,今后用${CC.Artifacts}表示。
(3) 你将在IP地址为:192.168.1.8,hostname为CI_Two的机器上部署第二个持续集成服务器。
(4) 把名为CI_One的机器做为主服务器,即把CI_Two上构建的日志(Logs)和结果(Artifacts)放发布到CI_One的${CC.Logs}和${CC.Artifacts}中。
(5) 你将在CI_Two上构建名为CI_TWO_connectFour的项目,它也使用SVN作为项目的SCM。
二、准备工作
(1) 将CI_One上的${CC.Logs}和${CC.Artifacts}设为共享目录,保证CI_Two对这两个目录可写。
(2)可以在CI_Two上,把CI_One的${CC.Logs}和${CC.Artifacts}两个目录分别映射成两个网络驱动器,名字为别为Z:/和Y:/。
(3) 将CI_One上的CruiseControl,整个拷贝到CI_Two上的C盘根目录下,即对于CI_Two来说,CruiseControl的工作目 录是C:/CruiseControl。(当然,也可以把一个你编译后生成的CruiseControl拷贝过来。)
(4) 在CI_Two的${CC.Projects}目录下,建立名为CI_TWO_connectFour的目录,把SVN中的源代码checkout到这个目录下,并确保build.xml文件在这个目录下。
三、第二台持续集成服务器的配置工作
以下所有工作全部发生在名为CI_Two这台机器上。
(4) 修改配置文件config.xml,如下所示:
<
cruisecontrol
>
<
project
name
="CI_TWO_connectFour"
>
<
listeners
>
<
currentbuildstatuslistener
file
="Z: /${project.name}/status.txt"
/><!-- 请注意这里的z: -->
</
listeners
>
<
bootstrappers
>
<
svnbootstrapper
localWorkingCopy
="projects/${project.name}"
/>
</
bootstrappers
>
<
modificationset
quietperiod
="30"
>
<
svn
localWorkingCopy
="projects/${project.name}"
/>
</
modificationset
>
<
schedule
interval
="300"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
</
schedule
>
<
log
dir="Z:/"
><!-- 请注意这里的z: -->
<
merge
dir
="projects/${project.name}/target/test-results"
/>
</
log
>
<
publishers
>
<
onsuccess
>
<
artifactspublisher
dest
="Y:/${project.name}"
file
="projects/${project.name}/target/connectfour.jar"
/>
<!-- 请注意这里的Y: -->
</ onsuccess >
</
publishers
>
</
project
>
</
cruisecontrol
>
(5) 修改启动脚本
打开CruiseControl.bat文件,找到下面这行代码:
set
EXEC
=
%JAVA_PATH% %CC_OPTS% -Djavax
.
management
.
builder
.
initial
=
mx4j
.
server
.
MX4JMBeanServerBuilder -jar
"
%LAUNCHER%
"
%* -jmxport
8000
-webport
8080
-rmiport
1099
将下面这段代码:
-dashboardrul http:
//
192.168
.
1.6
:
8080
/
dashboard
加入到上面的代码后,最后的代码如下:
set
EXEC
=
%JAVA_PATH% %CC_OPTS% -Djavax
.
management
.
builder
.
initial
=
mx4j
.
server
.
MX4JMBeanServerBuilder -jar
"
%LAUNCHER%
"
%* -jmxport
8000
-webport
8080
-rmiport
1099
-dashboardrul http:
//
192.168
.
1.6
:
8080
/
dashboard
保存该文件。
四、运行Build Grid
(1) 运行${CC.Home}中的CruiseControl.bat,启动CI_One上的CruiseControl。
(2) 运行${CC.Home}中的CruiseControl.bat,启动CI_Two上的CruiseControl。
五、访问你的Build Grid
(1) 在任何一台可以访问CI_One的机器上,通过浏览器访问下面的网址http://192.168.1.6:8080/dashboard 来查看你构建的项目。当然,使用hostname也可以。因为CruiseControl内置了一个Jetty应用服务器,版本是6.1。
(2) 你同样可以使用http://CI_One:8080/ 访问CI_One的Old Reporting 页面,用http://CI_Two:8080/ 访问CI_Two的Old Reporting。不过你会发现,你在CI_Two上配置的项目状态在CI_One的Old Reporting页面都显示为?????。这就是Old Reporting 的局限性。
小贴士:
(1) 要保证所有项目(无论在哪台机器上构建)的项目名都不相同,否则,CruiseControl的DashBoard只会选取其中的一个同名项目,忽略其它的同名项目。
(2) 在CI_Two中启动脚本中,一定要正确填写DashBoardURL参数,确保是CI_One的地址和端口。
(3) 如果项目的log文件比较大,或者构建时使用内存较多,在启动脚本中,可以通过增加JAVA命令行参数,扩大JVM的内存,以免出现OutOfMemory错误。
到此为止,CruiseControl的入门篇就结束了。以后,作为CruiseControl的进阶篇,我还将继续介绍CruiseControl的一些详细配置。
CruiseControl倾向于通过源码变化来自动触发构建,但仍提供了几种方式来定时触发构建,以达到nightly building的效果。
在开始配置之前,先说明一下config.xml文件中Project元素的相关属性。
- Project 元素对应你的一个需要构建的项目。其中:
-
- name为其指定名称。
-
- buildafterfailed 表示本次构建失败,是否要求CruiseControl继续下一次构建。
-
- 当设置为true时,表示如果本次构建失败,即使没有任何人检入代码,间隔时间一到,CruiseControl也构建它。
-
- 当设置为false时,表求如果本次构建失败,如果没有任何人检入代码,CruiseControl就不会再次构建它。
- requireModification表示是否需要源文件发生变化才进行构建。
-
- 当设置为true时,CruiseControl会根据modificationset元素的设置对源文件进行检查。
-
- 当设置为false时,CruiseControl仅根据Schedule元素的设置进行定时构建。
-
- 其默认值是ture。
- forceOnly表示是否只能手工构建。
-
- 当设置为true时,用户只能通过手工启动该项目的构建。
- 当设置为false时,表示即可以根据条件自动构建,用户也可以通过手工构建(前提dashboard-config.xml中的配置必须是true)。
- 其默认值是true。
为了达到定时构建的目的,首先要将Config.xml文件中该项目Project元素的requireModification属性设置为false。
一、每小时触发一次
设置Schedule元素的interval属性值为3600,即表示每小时会计划构建一次,由于requireModification已经设置为false,所以无论如何,CruiseControl都会构建。例如:
<
schedule
interval
="3600"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
</
schedule
>
二、每天触发一次
主要使用Schedule元素中各种构建器(如ant,nant,rake等)的time属性。time属性的格式为hhmm,例如2300就表示晚上十一点。
<
schedule
>
< ant anthome ="xxxxxx" buildfile ="projects/${project.name}/xxxxx.xml" time ="2300" />
</ schedule > 当然,也可以使用pause子元素来指定在某段时间内不构建,如白天上班时间不构建,其它时间每隔一小时构建一次可用如下配置表示:
<
schedule
interval
="3600"
>
<
ant
anthome
="apache-ant-1.7.0"
buildfile
="projects/${project.name}/build.xml"
/>
<
pause
starttime
="0800"
endtime
="1800"
/>
</
schedule
>
三、每周触发一次
主要使用Schedule元素中各种构建器(如ant,nant,rake等)的time及day属性。day属性值为英文星期几且大小写不敏感,如sunday。
<
schedule
>
< ant anthome ="apache-ant-1.7.0" buildfile ="projects/${project.name}/build.xml" day ="Sunday" />
</ schedule >
如果想指定具体时间,可以同时使用time属性,例如星期日的晚上十一点可以表示如下:
<
schedule
>
< ant anthome ="apache-ant-1.7.0" buildfile ="projects/${project.name}/build.xml" time ="2300" day ="Sunday" />
</ schedule >
需要说明的是,如果项目的构建时间长于指定的时间间隔,则构建次数会少于理想次数。例如设置每小时构建一次,但是每次构建要花费一个半小时。
另外,day属性还不支持多天,即不支持day="Monday Wednesday Friday"这种设置方式。如果想达到每隔一天构建一次的话,只能使用Schedule的interval属性。
个人建议,CruiseControl最好将触发事件写在各种Builder之外,而当前情况是:如果该Builder不支持time和day属性(如exec),可能就无法象上面所说的那样配置了。
持续集成的七项最佳实践
- 经常提交代码
- 不要提交无法构建的代码
- 立即修复无法继承的构建
- 编写自动化的开发者测试
- 必须通过所有的测试和审查
- 执行私有构建
- 避免迁出无法构建的代码
- 自动化构建
- 执行单命令构建
- 将构建脚本从IDE分离
- 集中放置软件资产
- 创建一致的目录结构
- 让构建快速失败
- 针对所有环境构建
- 使用专门的构建计算机
- 使用CI服务器
- 执行收购集成构建
- 执行快速构建
- 分阶段构建
- 自动化数据库集成
- 使用本地数据库沙盒
- 利用版本控制库共享数据库资产
- 让开发者能够修改数据库
- 让DBA成为开发的一员
- 自动化单元测试
- 自动化组件测试
- 自动化系统测试
- 自动化功能测试
- 对开发者测试分类
- 先执行最快的测试
- 为缺陷编写测试
- 让组件测试可重复
- 将测试用例限制为一个断言(一个test中只能有一个Assert..)
- 降低代码复杂度
- 持续进行设计复查
- 通过代码审查维持组织机构的标准
- 减少重复的代码
- 判断代码覆盖率
- 随时随地发布可工作的软件
- 为库中的资产打上标签
- 得到干净的环境
- 每一个构建版打上标签
- 执行所有测试
- 创建构建反馈报告
- 回滚构建的过程能力
- 使用持续反馈机制
在任何软件开发过程 中都有一个重要的部分: 得到可靠的软件创建(build ) 版本。尽管知道创建的重要性, 但是我们仍然会经常因为创建失败而惊讶不已。在这篇文章里, 我们将讨论Matt ( MatthewFoemmel ) 在ThoughtWorks 的一个重要项目中实施的过程, 这个过程在我们的公司里日益受到重视。它强调完全自动化的、可重复的创建过程, 其中包括每天运行多次的自动化测试。它让开发者可以每天进行系统集成, 从而减少了集成中的问题。
本文有以下主要内容:
· 持续集成的优点
· 集成越频繁, 效果越好
· 一次成功的创建是什么样的?
· 单一代码源
· 自动化创建脚本
· 自测试的代码
· 主创建
· 代码归还
· 总结
在软件开发的领域里有各种各样的“ 最佳实践” , 它们经常被人们谈起, 但是似乎很少有真正得到实现的。这些实践最基本、最有价值的就是: 都有一个完全自动化的创建、测试过程, 让开发团队可以每天多次创建他们的软件。“ 日创建” 也是人们经常讨论的一个观点, McConnell 在他的《快速软件开发》
中将日创建作为一个最佳实践来推荐, 同时日创建也是微软很出名的一项开发方法。但是, 我们更支持XP社群的观点: 日创建只是最低要求。一个完全自动化的过程让你可以每天完成多次创建, 这是可以做到的, 也是完全值得的。
在这里, 我们使用了“ 持续集成( Continuous Integration) ” 这个术语, 这个术语来自于XP( 极限编程) 的一个实践。但是我们认为: 这个实践早就存在, 并且很多并没有考虑XP 的人也在使用着它。只不过我们一直用XP 作为软件开发过程的标准, XP 也对我们的术语和实践产生了深远的影响。尽管如此,你还是可以只使用持续集成, 而不必使用XP 的任何其他部分— — 实际上, 我们认为: 对于任何切实可行的软件开发活动, 持续集成都是很基本的组成部分。实现自动化日创建需要做以下几部分的工作:
· 将所有的源代码保存在单一的地点, 让所有人都能从这里获取最新的源代码( 以及以前的版本)。
· 使创建过程完全自动化, 让任何人都可以只输入一条命令就完成系统的创建。
· 使测试完全自动化, 让任何人都可以只输入一条命令就运行一套完整的系统测试。
· 确保所有人都可以得到最新、最好的可执行文件。
所有这些都必须得到制度的保证。我们发现, 向一个项目中引入这些制度需要耗费相当大的精力。但是, 我们也发现, 一旦制度建立起来, 保持它的正常运转就不需要花多少力气了。
描述持续集成最大的难点在于: 它从根本上改变了整个开发模式。如果没有在持续集成的实践环境中工作过, 你很难理解它的开发模式。实际上, 在单独工作的时候, 绝大多数人都能感觉到这种气氛—— 因为他们只需要与自己的系统相集成。对于许多人来说, “ 团队开发” 这个词总让他们想起软件工程领域中的一些难题。持续集成减少了这些难题的数量, 代之以一定的制度。
持续集成最基本的优点就是: 它完全避免了开发者们的“ 除虫会议” — — 以前开发者们经常需要开这样的会, 因为某个人在工作的时候踩进了别人的领域、影响了别人的代码, 而被影响的人还不知道发生了什么, 于是bug就出现了。这种bug 是最难查的, 因为问题不是出在某一个人的领域里, 而是出在
两个人的交流上面。随着时间的推移, 问题会逐渐恶化。通常, 在集成阶段出现的bug 早在几周甚至几个月之前就已经存在了。结果, 开发者需要在集成阶段耗费大量的时间和精力来寻找这些bug 的根源。
如果使用持续集成, 这样的bug绝大多数都可以在引入的同一天就被发现。而且, 由于一天之中发生变动的部分并不多, 所以可以很快找到出错的位置。如果找不到bug 究竟在哪里, 你也可以不把这些讨厌的代码集成到产品中去。所以, 即使在最坏的情况下, 你也只是不添加引起bug 的特性而已。( 当
然, 可能你对新特性的要求胜过了对bug 的憎恨, 不过至少你可以多一种选择。)
到现在为止, 持续集成还不能保证你抓到所有集成时出现的bug。持续集成的排错能力取决于测试技术, 众所周知, 测试无法证明已经找到了所有的错误。关键是在于: 持续集成可以及时抓到足够多的bug,这就已经值回它的开销了。
所以, 持续集成可以减少集成阶段“ 捉虫” 消耗的时间, 从而最终提高生产力。尽管现在还不知道是否有人对这种方法进行过科学研究, 但是作为一种实践性的方法, 很明显它是相当有效的。持续集成可以大幅减少耗费在“ 集成地狱” 中的时间, 实际上, 它可以把地狱变成小菜一碟。
持续集成有一个与直觉相悖的基本要点: 经常性的集成比很少集成要好。对于持续集成的实践者来说, 这是很自然的; 但是对于从未实践过持续集成的人来说, 这是与直观印象相矛盾的。
如果你的集成不是经常进行的( 少于每天一次) , 那么集成就是一件痛苦的事情, 会耗费你大量的时间与精力。我们经常听见有人说: “ 在一个大型的项目中, 不能应用日创建” , 实际上这是一种十分愚蠢的观点。
不过, 还是有很多项目实践着持续集成。在一个五十人的团队、二十万行代码的项目中, 我们每天要集成二十多次。微软在上千万行代码的项目中仍然坚持日创建。
持续集成之所以可行, 原因在于集成的工作量是与两次集成间隔时间的平方成正比的。尽管我们还没有具体的衡量数据, 但是可以大概估计出来: 每周集成一次所需的工作量绝对不是每天集成的5 倍,而是大约25 倍。所以, 如果集成让你感到痛苦, 也许就说明你应该更频繁地进行集成。如果方法正确,更频繁的集成应该能减少你的痛苦, 让你节约大量时间。
持续集成的关键是自动化。绝大多数的集成都可以而且应该自动完成。读取源代码、编译、连接、测试, 这些都可以自动完成。最后, 你应该得到一条简单的信息, 告诉你这次创建是否成功: “ yes” 或“ no” 。如果成功, 本次集成到此为止; 如果失败, 你应该可以很简单地撤消最后一次的修改, 回到前一次成功的创建。在整个创建过程中, 完全不需要你动脑子。
如果有了这样一套自动化过程, 你随便想多频繁进行创建都可以。唯一的局限性就是创建过程本身也会消耗一定的时间。( 译注: 不过与捉虫所需的时间比起来, 这点时间是微不足道的。)
有一件重要的事需要确定: 怎样的创建才算是成功的? 看上去很简单, 但是如此简单的事情有时却会变得一团糟, 这是值得注意的。有一次, Martin Fowler 去检查一个项目。他问这个项目是否执行日创建, 得到了肯定的回答。幸亏Ron Jeffries 也在场, 他又提了一个问题: “ 你们如何处理创建错误? ” 回答是: “ 我们给相关的人发一个e-mail。” 实际上, 这个项目已经好几个月没有得到成功的创建了。这不是日创建, 这只是日创建的尝试。
对于下列“ 成功创建” 的标准, 我们还是相当自信的:
· 所有最新的源代码都被配置管理系统验证合格
· 所有文件都通过重新编译
· 得到的目标文件( 在我们这里就是Java 的class 文件) 都通过连接, 得到可执行文件
· 系统开始运行, 针对系统的测试套件( 在我们这里大概有150 个测试类) 开始运行
· 如果所有的步骤都没有错误、没有人为干涉, 所有的测试也都通过了, 我们就得到了一个成功的创建
绝大多数人都认为“ 编译+连接=创建” 。至少我们认为: 创建还应该包括启动应用程序、针对应用程序运行简单测试( McConnell 称之为“ 冒烟测试” : 打开开关让软件运行, 看它是否会“ 冒烟” ) 。运行更详尽的测试集可以大大提高持续集成的价值, 所以我们会首选更详尽的测试。
为了实现每日集成, 任何开发者都需要能够很容易地获取全部最新的源代码。以前, 如果要做一次集成, 我们就必须跑遍整个开发中心, 询问每一个程序员有没有新的代码, 然后把这些新代码拷贝过来,再找到合适的插入位置… … 没有什么比这更糟糕的了。
办法很简单。任何人都应该可以带一台干净的机器过来, 连上局域网, 然后用一条命令就得到所有的源文件, 马上开始系统的创建。
最简单的解决方案就是: 用一套配置管理( 源代码控制) 系统作为所有代码的来源。配置管理系统通常都设计有网络功能, 并且带有让开发者轻松获取源代码的工具。而且, 它们还提供版本管理工具,这样你可以很轻松地找到文件以前的版本。成本就更不成问题了, CVS 就是一套出色的开放源代码的配置管理工具。
所有的源文件都应该保存在配置管理系统中。我说的这个“ 所有” 常常比人们想到的还要多, 它还包括创建脚本、属性文件、数据库调度DLL、安装脚本、以及在一台干净的机器上开始创建所需的其他一切东西。经常都能看到这样的情况: 代码得到了控制, 但是其他一些重要的文件却找不到了。
尽量确保所有的东西都保存在配置管理系统的同一棵代码源树中。有时候为了得到不同的组件, 人们会使用配置管理系统中不同的项目。这带来的麻烦就是: 人们不得不记住哪个组件的哪个版本使用了其他组件的哪些版本。在某些情况下, 你必须将代码源分开, 但是这种情况出现的几率比你想象的要小
得多。你可以在从一棵代码源树创建多个组件, 上面那些问题可以通过创建脚本来解决, 而不必改变存储结构。
如果你编写的是一个小程序, 只有十几个文件, 那么应用程序的创建可能只是一行命令的事: javac*.java。更大的项目就需要更多的创建工作: 你可能把文件放在许多目录里面, 需要确保得到的目标代码都在适当的位置; 除了编译, 可能还有连接的步骤; 你可能还从别的文件中生成了代码, 在编译之前需要先生成; 测试也需要自动运行。
大规模的创建经常会耗费一些时间, 如果只做了一点小小的改动, 当然你不会希望重新做所有这些步骤。所以好的创建工具会自动分析需要改变的部分, 常见的方法就是检查源文件和目标文件的修改日期, 只有当源文件的修改日期迟于目标文件时, 才会重新编译。于是, 文件之间的依赖就需要一点技巧了: 如果一个目标文件发生了变化, 那么只有那些依赖它的目标文件才会重编译。编译器可能会处理这类事情, 也可能不会。
取决于自己的需要, 你可以选择不同的创建类型: 你创建的系统可以有测试代码, 也可以没有, 甚至还可以选择不同的测试集; 一些组件可以单独创建。创建脚本应该让你可以根据不同的情况选择不同的创建目标。
你输入一行简单的命令之后, 帮你挑起这副重担常常是脚本。你使用的可能是shell脚本, 也可能是更复杂的脚本语言( 例如Perl 或Python) 。但是很快你就会发现一个专门设计的创建环境是很有用的,例如Unix下的make工具。
在我们的Java开发中, 我们很快就发现需要一个更复杂的解决方案。Matt 用了相当多的时间开发了一个用于企业级Java 开发的创建工具, 叫做Jinx。但是, 最近我们已经转而使用开放源代码的创建工具Ant http://jakarta.apache.org/ant/index.html) 。Ant 的设计与Jinx 非常相似, 也支持Java文件编译和Jar封装。同时, 编写Ant 的扩展也很容易, 这让我们可以在创建过程中完成更多的任务。
许多人都使用IDE, 绝大多数的IDE中都包含了创建管理的功能。但是, 这些文件都依赖于特定的IDE, 而且经常比较脆弱, 而且还需要在IDE 中才能工作。IDE 的用户可以建立自己的项目文件, 并且在自己的单独开发中使用它们。但是我们的主创建过程用Ant 建立, 并且在一台使用Ant的服务器上运行。
只让程序通过编译还是远远不够的。尽管强类型语言的编译器可以指出许多问题, 但是即使成功通过了编译, 程序中仍然可能留下很多错误。为了帮助跟踪这些错误, 我们非常强调自动化测试— — 这也是XP 提倡的另一个实践。
XP将测试分为两类: 单元测试和容纳测试( 也叫功能测试) 。单元测试是由开发者自己编写的, 通常只测试一个类或一小组类。容纳测试通常是由客户或外部的测试组在开发者的帮助下编写的, 对整个系统进行端到端的测试。这两种测试我们都会用到, 并且尽量提高测试的自动化程度。
作为创建的一部分, 我们需要运行一组被称为“ BVT” ( Build Verification Tests, 创建确认测试) 的测试。BVT 中所有的测试都必须通过, 然后我们才能宣布得到了一个成功的创建。所有XP 风格的单元测试都属于BVT。由于本文是关于创建过程的, 所以我们所说的“ 测试” 基本上都是指BVT。请记住,除了BVT 之外, 还有一条测试线存在( 译注: 指功能测试) , 所以不要把BVT 和整体测试、QA等混为一谈。实际上, 我们的QA 小组根本不会看到没有通过BVT 的代码, 因为他们只对成功的创建进行测试。
有一条基本的原则: 在编写代码的同时, 开发者也应该编写相应的测试。完成任务之后, 他们不但要归还( check in) 产品代码, 而且还要归还这些代码的测试。这也跟XP 的“ 测试第一” 的编程风格很相似: 在编写完相应的测试、并看到测试失败之前, 你不应该编写任何代码。所以, 如果想给系统添加新特性, 你首先应该编写一个测试。只有当新的特性已经实现了以后, 这个测试才可能通过。然后, 你的工作就是让这个测试能够通过。
我们用Java 编写这些测试, 与开发使用同样的语言, 所以编写测试与编写代码没有太大的区别。我们使用JUnit( http://www.junit.org/) 来作为组织、编写测试的框架。JUnit是一个简单的框架, 让我们可以快速编写测试、将测试组织为套件、并以交互或批处理的模式来运行测试套件。( JUnit 是xUnit家族的Java 版本— — xUnit包括了几乎所有语言的测试框架。)
在编写软件的过程中, 在每一次的编译之后, 开发者通常都会运行一部分单元测试。这实际上提高了开发者的工作效率, 因为这些单元测试可以帮助你发现代码中的逻辑错误。然后, 你就没必要去调试查错, 只需要注意最后一次运行测试之后修改的代码就行了。这个修改的范围应该很小, 所以寻找bug也就容易多了。
并非所有的人都严格遵循XP“ 测试第一” 的风格, 但是在第一时间编写测试的好处是显而易见的。它们不但让每个人的工作效率更高, 而且由这些测试构成的BVT 更能捕捉到系统中的错误。因为BVT每天要运行好几次, 所以BVT 检查出的任何问题都是比较容易改正的, 原因很简单: 我们只做了相当小
范围的修改, 所以我们可以在这个范围内寻找bug。在修改过的一小块代码中排错当然比跟踪整个系统来排错要有效多了。
当然, 你不能指望测试帮你找到所有的问题。就象人们常说的: 测试不能证明系统中不存在错误。但是, 尽善尽美不是我们唯一的要求。不够完美的测试只要经常运行, 也比永远写不出来的“ 完美测试”要好得多。
另一个相关的问题就是: 开发者们为自己的代码编写测试。我们经常听人说: 开发者不应该测试自己的代码, 因为他们很容易忽视自己工作中的错误。尽管这也是事实, 但是自测试过程需要快速将测试转入代码基础中。这种快速转换的价值超过独立测试者的价值。所以, 我们还是用开发者自己编写的测试来构造BVT, 但是仍然有独立编写的容纳测试。
自测试另一个很重要的部分就是它通过反馈— — XP 的一项核心价值— — 来提高测试的质量。这里的反馈来自于从BVT 中逃脱的bug。自测试的规则是: 除非你在BVT 中加入了相应的测试, 否则就不能修正任何错误。这样, 每当要修正某个错误的时候, 你都必须添加相应的测试, 以确保BVT 不会再把错误放过去。而且, 这个测试应该引导你去考虑更多的测试、编写更多的测试来加强BVT。
创建过程的自动化对于单个开发者来说很有意义, 但是它真正发光的, 还是在整个系统的主创建( master build) 的生成。我们发现, 主创建过程能让整个团队走到一起来, 让他们及早发现集成中的问题。
第一步是要选择运行主创建的机器。我们选择了一台叫做“ 投石车” 的计算机( 我们经常玩“ 帝国时代” J) , 这是一台装有四个CPU的服务器, 非常适合专门用来做创建。( 由于完整的创建需要相当长的时间, 所以这种马力是必须的。)
创建进程是在一个随时保持运行的Java 类中进行的。如果没有创建任务, 创建进程就一直循环等待,每过几分钟去检查一下代码仓库。如果在最后的创建之后没有人归还任何代码, 进程就继续等待。如果代码仓库中有了新的代码, 就开始创建。
创建的第一阶段是完全提取仓库中的代码。Starteam已经为我们提供了相当好的Java API, 所以切入代码仓库也很容易。守护进程( daemon) 会观察五分钟以前的仓库, 看最近五分钟里面有没有人归还了代码。如果有, 守护进程就会考虑等五分钟再提取代码( 以免在别人归还代码的过程中提取) 。
守护进程将全部代码提取到投石机的一个目录中。提取完成之后, 守护进程就会在这个目录里调用Ant脚本。然后, Ant 会接管整个创建过程, 对所有源代码做一次完整的创建。Ant脚本会负责整个编译过程, 并把得到的class文件放进六个jar 包里, 发布到EJB 服务器上。
当Ant 完成了编译和发布的工作之后, 创建守护进程就会在EJB服务器上开始运行新的jar, 同时开始运行BVT 测试套件。如果所有的测试都能正常运行通过, 我们就得到了一个成功的创建。然后创建守护进程就会回到Starteam, 将所有提取出的源代码标记上创建号。然后, 守护进程会观察创建过程中是否还有人归还了代码。如果有, 就再开始一次创建; 如果没有, 守护进程就回到它的循环中, 等待下一次的归还。
创建结束之后, 创建守护进程会给所有向最新一次创建归还了代码的开发者发一个e-mail, 汇报创建的情况。如果把创建留在代码归还之后去做, 而又不用e-mail 向开发者通报创建的情况, 我们通常认为这是不好的组织形式。
守护进程将所有的步骤都写在XML 格式的日志文件里面。投石车上会运行一个servlet, 允许任何人通过它检查日志, 以观察创建的状态。( 见图1)
屏幕上会显示出创建是否正在运行、开始运行的时间。在左边有所有创建的历史记录, 成功的、失败的都记录在案。点击其中的某一条记录, 就会显示出这次创建的详细信息: 编译是否通过、测试的结果、发生了哪些变化… …
我们发现很多开发者都经常看看这个页面, 因为它让他们看到项目发展的方向, 看到随着人们不断归还代码而发生的变化。有时我们也会在这个页面上放一些其他的项目新闻, 但是需要把握好尺度。
要让开发者能在自己的本地机器上模拟主创建过程, 这是很重要的。这样, 如果集成错误出现了,开发者可以在自己的机器上研究、调试, 而不必真的执行主创建过程。而且, 开发者也可以在归还代码之前先在本地执行创建, 从而降低了主创建失败的可能性。
这里有一个比较重要的问题: 主创建应该是干净的创建( 完全从源代码开始) 还是增量创建? 增量创建会快得多, 但是也增大了引入错误的风险, 因为有些部分是没有编译的。而且我们还有无法重新创建的风险。我们的创建速度相当快( 20 万行代码约15 分钟) , 所以我们乐于每次都做干净的创建。但是,有些团队喜欢在大多数时候做增量创建, 但是当那些奇怪的问题突然出现时, 也经常性地做干净的创建( 至少每天一次) 。
使用自动化创建就意味着开发者应该遵循某种节奏来开发软件, 最重要的就是他们应该经常集成。他们曾经见过一些组织, 他们也做日创建, 但是其中的开发者却不经常归还代码。如果开发者几周才归还一次代码, 那么日创建又有什么意义呢? 我们遵循的原则是: 每个开发者至少每天要归还一次代码。
在开始新的任务之前, 开发者应该首先与配置管理系统同步。也就是说, 他们应该首先更新本地机器上的源代码。在旧的代码基础上编写代码, 这只会带来麻烦和混乱。
然后, 开发者要随时保持文件的更新。开发者可以在一段任务完成之后将代码集成到整个系统中,也可以在任务的中途集成, 但是在集成的时候必须保证所有的测试都能通过。集成的第一步是要再次使开发者的本地文件与代码仓库同步。代码仓库中所有新近有改动的文件都要拷贝到开发者 的工作目录中来, 当文件发生冲突时, 配置管理系统会向开发者提出警告。然后, 开发者需要对同步后的工作集进行创建, 对这些文件运行BVT, 并得到正确的结果。
现在, 开发者可以把新的文件提交到代码仓库中。提交完成之后, 开发者就需要等待主创建。如果主创建成功, 那么这次归还也是成功的。如果主创建失败了, 开发者可以在本地修改。如果修改很简单,就可以直接提交; 如果修改比较复杂, 开发者就需要放弃这次修改, 重新同步自己的工作目录, 然后继
续在本地开发、调试, 然后再次提交。
某些系统强制要求归还进程逐个进行。在这种情况下, 系统中会有一个创建令牌, 同一时间只有一个开发者能拿到令牌。开发者获取创建令牌, 再次同步文件, 提交修改, 然后释放令牌。这就确保创建过程中, 最多只能有一个开发者在更新代码仓库。不过我们发现, 即使没有创建令牌, 我们也很少遇到麻烦, 所以我们也不用这种方法。经常会有多个人同时向同一个主创建提交代码的情况, 但是这很少造成创建失败, 而且这样的错误也很容易修复。
同时, 我们还让开发者自己来决定归还过程中的小心程度。这反映出开发者对集成错误出现几率的评估。如果她觉得很有可能出现集成错误, 那么她就会在归还之前先做一次本地创建; 如果她觉得根本不可能出现集成错误, 那么她可以直接归还。如果犯了错误, 在主创建运行时她立刻就会发现, 然后她就必须放弃自己的修改, 找到出错的地方。如果错误很容易发现、很容易修补, 那么这种错误也是可以接受的。
1274

被折叠的 条评论
为什么被折叠?



