Martin的《持续集成》
相信非常多读者和我一样。最早接触到持续集成的概念是来自Martin的著名文章《持续集成》。该文最早公布于2000年9月,之后在2006年进行了一次修订。它清晰地解释了持续集成的概念。并总结了10条实践,它们分别为:
-
仅仅维护一个源代码仓库
-
自己主动化构建
-
让构建自行測试
-
每人每天向主干提交代码
-
每次提交都应在持续集成机器上构建主干
-
保持高速的构建
-
在模拟生产环境中測试
-
让每一个人都能轻易获得最新的可运行文件
-
每一个人都能看到进度
-
自己主动化部署
原始文章距今已10年有余,这在软件行业中算是非常长的时间了,但我们都能看到Martin总结的这些实践依然闪耀着光芒,依然有非常多团队在努力实践它们并得到了丰厚的回报,当然也有非常多团队由于各种原因拒绝实践持续集成从而无法体会到个中优点。
从这10条实践中我们能找到非常多流行开源工具的影子。比如版本号控制工具cvs、svn、git,自己主动化构建工具Maven、Ant。自己主动化測试框架JUnit、TestNG,以及持续集成serverCruiseControl和Hudson等等。
事实上不论你是否实践持续集成,单独使用这当中的非常多工具都能发挥极大的价值。持续集成的一大意义在于它引入了一个有效的流程。能让这些工具有机融合。并相互促进。
关于持续集成另一本获得Jolt大奖的图书。名为《持续集成——软件质量改进和风险减少之道》。
但不管是Martin的文章,还是这本图书。都没有阐述使用Maven作为自己主动化构建工具实施持续集成的细节。本文旨在介绍一些基于Maven实施持续集成的实践,希望这些经验能从详细处帮助到读者。
架设私有Maven仓库
Martin的文章并没有涉及到依赖管理的内容。但在Java的世界中。依赖管理是开发者不得不面对的问题。不管是外部的开源类库依赖,还是项目内部的模块间依赖,都须要有效地管理。能够说依赖管理是持续集成核心的内容之中的一个。
Maven通过其依赖管理机制和随处可用的中央仓库有效地攻克了这个问题,用户仅仅须要在POM中声明项目所须要的依赖。Maven就能在构建的时候自己主动从仓库解析依赖。
只是只这样是不够的。我们知道,持续集成的最大优点在于减少风险,简单地来说就是尽早暴露问题,能让开发者及早发现并修复,从而减少修复成本。但是,假设每一个人都从中央仓库反复下载依赖,这是很耗时的。集成的反馈周期肯定会延长。
我已经无数次听到有人抱怨“Maven在下载整个Internet!
”。
构建要快!
持续集成反馈要快。Maven你不能拖慢这个流程。
幸运的是开源世界有非常好的解决方式,仅仅要使用Maven仓库管理器软件如Nexus建立一个私有的Maven仓库,问题就能迎刃而解。原理非常easy,这个位于局域网内的Maven仓库可以代理全部外部仓库,从而避免全部人从Internet反复下载依赖文件。
这样Maven解析依赖的时候仅限于局域网,构建速度就大大地加快了。
比如大家都须要使用junit-4.8.2.jar
,当第一个人向私有仓库请求的时候,私有仓库从中央库下载并缓存下来,如果耗时10s。之后其它人须要junit-4.8.2.jar
的时候,私有仓库直接使用缓存的文件,这个耗时可能就是1s。如果有100个开发者使用该文件,那节省的时间就是
100 * 10 - ( 10 + 99 * 1 ) = 891s 。实际情况中依赖的数量可能会是成百上千,那节省的时间就变得很的可观。
或许有人会说,我也全然能够将项目依赖增加到版本号控制中。这一点甚至在《卓有成效的程序猿》中都被明白提及,在该书第5章的"DRY版本号控制"一节中,Neal Ford有这么一段话:“全部用来构建项目的东西都应该被放入版本号控制,包含二进制文件(类库。框架,JAR文件,构建脚本等等)”。作者进一步解释了其目的,这么做可以保证项目不受外部因素影响(如依赖版本号变化。甚至丢失),保证构建的稳定,作者也同一时候提及了一般版本号控制工具处理二进制文件的性能问题。抛开这条结论性的实践,细致考虑其目的,我们就能发现。私有Maven仓库相同能保证构建的稳定。并且能避免版本号控制工具处理二进制文件而造成的潜在性能问题。所以。我斗胆说一句,Neal Ford所提的这条实践OUT了!
私有Maven的仓库的意义还不仅限于此,结合自己主动化部署和Maven的SNAPSHOT机制,它能大大促进项目集成的效率。
在模块化的开发环境中。大家各司其职,专注于自己所负责的模块,持续集成的规则是,在往版本号控制提交代码前。须要先保证本地构建没有问题。那一般的做法就是更新全部模块的代码并构建。但是,真的须要构建那些事实上你并不怎么关心的模块么?且不谈一旦构建他人代码时出错。你往往会不知所措,这样的做法同一时候也添加了本地构建的时间。
Maven有SNAPSHOT版本号的概念,其目的就是让你可以构建一个暂时的版本号。供团队他人使用。这样他们就不必在代码的层次关心自己的依赖。于是私有Maven仓库就充当了一个中介的作用。而持续集成server就多了一个职责。每次它成功构建一个模块,都应该将该模块的SNAPSHOT版本号公布到Maven仓库中。如今。大家就不用去构建别人的代码了。Maven能自己主动帮你从私有仓库解析下载依赖的最新SNAPSHOT(使用mvn命令的-U參数强制更新)。注意,除了持续集成server外,不论什么其它人都不应该公布SNAPSHOT版本号到Maven仓库,由于仅仅有持续集成server的环境是可信任的,你能在本地成功运行mvn clean install并不代表持续集成server上该命令能成功,因为每一个人的本地环境各有差异,因此集成的成功与否应当以持续集成server为准,而仅仅有集成成功后。SNAPSHOT才干够被部署到私有仓库供他人使用。
鉴于上述的原因分析。我觉得在基于Maven的持续集成环境中。再怎么强调私有Maven仓库的重要性都是不为过的。
正确的集成命令
在持续集成server上使用如何的 mvn 命令集成项目,这个问题乍一看答案非常显然,不就是 mvn clean install 么?其实比較好的集成命令会略微复杂些。以下是一些总结:
-
不要忘了clean: clean可以保证上一次构建的输出不会影响到本次构建。
-
使用deploy而不是install: 构建的SNAPSHOT输出应当被自己主动部署到私有Maven仓库供他人使用,这一点在前面已经具体论述。
-
使用-U參数: 该參数能强制让Maven检查全部SNAPSHOT依赖更新,确保集成基于最新的状态。假设没有该參数。Maven默认以天为单位检查更新,而持续集成的频率应该比这高非常多。
-
使用-e參数:假设构建出现异常,该參数能让Maven打印完整的stack trace,以方便分析错误原因。
-
使用-Dmaven.repo.local參数:假设持续集成server有非常多任务,每一个任务都会使用本地仓库,下载依赖至本地仓库。为了避免这种多线程使用本地仓库可能会引起的冲突。能够使用-Dmaven.repo.local=/home/juven/ci/foo-repo/这种參数为每一个任务分配本地仓库。
-
使用-B參数:该參数表示让Maven使用批处理模式构建项目,可以避免一些须要人工參与交互而造成的挂起状态。
综上,持续集成server上的集成命令应该为 mvn clean deploy -B -e -U -Dmaven.repo.local=xxx 。此外,定期清理持续集成server的本地Maven仓库也是个非常好的习惯。这样能够避免浪费磁盘资源,差点儿全部的持续集成server软件都支持本地的脚本任务,你能够写一行简单的shell或bat脚本,然后配置以天为单位自己主动清理仓库。须要注意的是,这么做的前提是你有私有Maven仓库。否则每次都从Internet下载全部依赖会是一场噩梦。
用好Profile
假设不须要考虑各种不同的环境, 并且你的自己主动測试(包含集成測试)跑得飞快,那你就不用为项目建立多个集成任务。但实际的情况是。集成的时候可能要考虑各种环境,比如开发环境、測试环境、产品环境。而当项目越来越大。測试越来越多,控制构建时间在一个可接受的范围内(比如10分钟)变得越来越不现实。
《持续集成——软件质量改进和风险减少之道》中介绍了一种名为分阶段构建(staged build)的解决方式,比如你可以将构建分为两个部分,第一部分包含了编译和单元測试等可以高速结束的任务,第二个部分包含集成測试等耗时较长的任务,仅仅有第一部分成功完毕后,才触发第二部分集成。这么做的意义在于让持续集成的反馈尽可能的快。
Maven的Profile机制可以非常好的支持分阶段构建。比如,借助Maven Surefire Plugin,你可以统一单元測试命名为**UT
。统一集成測试命名为**IT
。然后配置Maven
Surefire Plugin默认仅仅执行单元測试,然后再编写一个名为integrationTest的Profile。在当中配置Maven Surefire Plugin执行集成測试。
然后再以此为基础分阶段构建项目,第一个构建为 mvn clean install -B -e -U ,第二个构建任务为 mvn clean deploy -B -e -U -PintegrationTest 。前一个构建成功后再触发第二个构建,然后才部署至Maven仓库。值得一提的是,Maven Surefire Plugin可以非常好支持JUnit 3、JUnit 4和TestNG,你可以依照最适合自己的方式来划分单元測试和集成測试。
还有一个常见的分阶段构建案例是生成Maven网站,使用 mvn clean site 生成网站往往比較耗时且耗资源,这种任务相应的持续集成中的持续审查阶段,该阶段往往不须要非常高的集成频率。你会希望每10分钟就检查源码变更并编译測试,但非常少有人会希望每10分钟让系统生成一次測试覆盖率报告、CheckStyle报告等内容,因此合理的做法是使用一个较低的频率。比如每天,这样能够避免无谓的资源消耗。更重要的是,这样不会拖慢本该非常快的编译和单元測试等反馈内容。
另一些情况是系统须要基于不同环境进行集成,这时候就须要用到Maven的属性机制、资源过滤、以及前面提到的Profile。篇幅原因,这里不再展开。
小结
持续集成是敏捷最重要的实践之中的一个,但怎样在基于Maven的环境下实践持续集成却鲜有文章详述。本文介绍了一些该主题的最佳实践。包含架设私有仓库、使用正确的集成命令、利用Profile等技术处理分阶段构建等等。本文旨在让广大Maven用户认识到这些实践的存在及重要性。并没有详解一些诸如Nexus安装配置、Maven Surefire Plugin配置、或者说Profile配置使用方面的细节。假设你希望看到更细节的介绍,能够參考我的《Maven实战》一书。
除了上面的内容之外,该书还详解了怎样使用Hudson(或许该改称Jenkins了)这一最流行的开源持续集成server。
当然。假设你有关于Maven和持续集成方面的经验,也请不吝分享。