持续集成一个简单总结
2010-10-08 20:32
453 查看
一、理论篇:
持续集成鼓励尽量短周期内项目团队的代码提交,同时保证每次check in都不会损害我们的构建通过。它跟每日构建的区别就在于代码提交频率更高(一般为一个小时),构建的频率也更高,这样做的目的就是为了快速反馈,使得BUG越早被发现,并能以邮件或者消息(甚至短信)的形式快速反馈给开发人员,从而快速解决问题,并保证构建成功。
二、工具篇:
持续集成重在COC(Conversion Over Configuration:约定由于配置),这样选择合适的支持持续集成的工具就相当重要。庆幸的是我们有许多开源的选择,但是首先我们需要了解持续集成的实现架构:
从上图中我们看到,客户端提交代码更改到源代码仓库,CI服务器会检测到代码库的修改,它会检出代码,在本地构建,构建成功,会将构建结果反馈回客户端,同时可能将构建的可运行代码发布到WEB服务器上。
所以,我们就需要各个节点的工具支持:
对于SCM工具,我们的选择的开源工具有CVS、SVN等,这也没有特殊的取舍,就自己的爱好和公司的已有平台而定。我们这里假设使用SVN作为版本管理工具,它的中文站是:http://www.subversion.org.cn/ ;
对于构建工具,我们的选择的开源工具有Ant和Maven等,Ant通过一些内置的和扩展的Task来实现包括文件操作、编译、测试、代码检查、打包等操作,Eclipse默认提供了对Ant的支持。通过在build.xml中配置一系列相互依赖的任务来实现我们定义的构建过程。Maven是一个以项目为模型的构建,项目管理工具,注意它并不是为了替代Ant(同时支持运行Ant脚本),而是以另一种视觉提供了对软件生命周期的管理,它通过插件的方式提供了类似于Ant任务的功能,它的特色之处在于对项目依赖组件的统一管理,同时它的生成站点功能也是一个不错的特性,具体不再赘述,后面的构建我们会分别用Ant和Maven来说明。
对于持续集成工具,我们的选择的开源工具有CruiseControl(后面简称CC)和Hudson。BuildLoop是CC的核心, 这个BuildLoop包含插件支持,详细介绍可以参照附件中的电子书。CC的实施结构如下图所示:
为了完成上面的结构,CC提供的插件按照下图所示的流程完成构建:
通过对CC的cruisecontrol项目的配置,支持图形化显示checkstyle, pmd, findbugs, cobertura, javadoc等报告。同时CC-Config也提供了对项目的图形化配置,比较方便。
Hudson也提供了持续集成服务器的大多数功能,详细参考官方站点:https://hudson.dev.java.net/
三、实践篇:
我们模拟了两个项目,一个AntBasedCI是基于Ant构建的客户端应用程序,一个MavenBasedCI是基于Maven构建的Web应用程序,我们的SCM由SVN自带的Server提供,启动这个服务,我们需要在命令行运行: svnserve -d -r D:/repos 其中-r指定repository磁盘位置。
CC的项目配置:
Xml代码
<cruisecontrol>
<project requiremodification="false" forceonly="false" name="MavenBasedCI">
<modificationset QuietPeriod="30">
<svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" />
</modificationset>
<schedule Interval="300">
<maven2 Goal="-e clean site install" MvnHome="D:/OpenSource/maven-2.0.4" PomFile="${checkout.dir}/${project.name}/pom.xml" ShowProgress="false" />
</schedule>
<bootstrappers>
<svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" />
</listeners>
<log>
<merge Dir="${logs.dir}/${project.name}" />
<merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" />
</log>
<publishers>
<onsuccess>
<artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" />
</onsuccess>
<artifactspublisher File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" Dest="artifacts/${project.name}" />
<artifactspublisher Dir="${checkout.dir}/${project.name}/target/site" Dest="artifacts/${project.name}" />
</publishers>
<property name="checkout.dir" value="${basedir}/checkout" />
<property name="logs.dir" value="${basedir}/logs" />
<property name="artifact.dir" value="${basedir}/artifacts" />
</project>
<project requiremodification="false" forceonly="false" name="AntBasedCI">
<modificationset QuietPeriod="30">
<svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" />
</modificationset>
<schedule Interval="300">
<ant AntHome="D:/OpenSource/apache-ant-1.7.1" BuildFile="${checkout.dir}/${project.name}/build.xml" Target="all" />
</schedule>
<bootstrappers>
<svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" />
</listeners>
<log>
<merge Dir="${logs.dir}/${project.name}" />
<merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" />
</log>
<publishers>
<onsuccess>
<artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" />
</onsuccess>
<artifactspublisher Dest="artifacts/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" />
<artifactspublisher Dir="${checkout.dir}/${project.name}/target" Dest="artifacts/${project.name}" />
</publishers>
</project>
<dashboard />
<property name="basedir" value="E:/CI/ccworkspace" />
<property name="checkout.dir" value="${basedir}/checkout" />
<property name="logs.dir" value="${basedir}/logs" />
<property name="artifact.dir" value="${basedir}/artifacts" />
</cruisecontrol>
上面的配置是通过CC-Config图形配置自动生成的,包含我们的两个工程。
Ant配置build.xml
Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<project name="AntBasedCI" default="all">
<property name="default.target.dir" value="target" />
<property name="classes.dir" value="${default.target.dir}/classes" />
<property name="test.classes.dir" value="${default.target.dir}/test-classes" />
<property name="test.report.dir" value="${default.target.dir}/test-reports" />
<property name="lib.dir" value="${basedir}/lib" />
<property name="javadoc.dir" value="${default.target.dir}/apidocs" />
<property name="source.dir" value="src" />
<property name="test.source.dir" value="test" />
<property name="test.pattern" value="**/**Test.java" />
<!-- Coverage reports are deposited into these directories -->
<property name="cobertura.dir" value="${default.target.dir}/cobertura"/>
<!-- Instrumented classes are deposited into this directory -->
<property name="instrumented.dir" value="instrumented" />
<path id="cobertura.classpath">
<fileset dir="${lib.dir}">
<include name="*.jar" />
</fileset>
</path>
<taskdef classpathref="cobertura.classpath" resource="tasks.properties"/>
<target name="clean">
<delete dir="${classes.dir}"/>
<delete dir="${test.classes.dir}"/>
<delete dir="${default.target.dir}"/>
</target>
<target name="init" depends="clean">
<mkdir dir="${classes.dir}" />
<mkdir dir="${test.classes.dir}" />
<mkdir dir="${javadoc.dir}" />
<mkdir dir="${default.target.dir}"/>
<mkdir dir="${instrumented.dir}"/>
<path id="build.classpath">
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${default.target.dir}">
<include name="**/*.jar" />
</fileset>
</path>
</target>
<target name="compile-source" depends="init" description="compiles all .java files in source directory ">
<javac destdir="${classes.dir}" srcdir="${source.dir}" classpathref="build.classpath" />
</target>
<target name="instrument" depends="compile-source">
<delete file="cobertura.ser"/>
<delete dir="${instrumented.dir}" />
<!--Instrument the application classes, writing the instrumented classes into ${build.instrumented.dir}.-->
<cobertura-instrument todir="${instrumented.dir}">
<ignore regex="org.apache.log4j.*" />
<fileset dir="${classes.dir}">
<!-- Instrument all the application classes, but don't instrument the test classes.-->
<include name="**/*.class" />
<exclude name="**/*Test.class" />
</fileset>
</cobertura-instrument>
</target>
<target name="jar" depends="instrument">
<jar jarfile="${default.target.dir}/${ant.project.name}.jar" basedir="${classes.dir}" />
</target>
<target name="compile-tests" depends="jar" description="compiles all .java files in test directory ">
<javac destdir="${test.classes.dir}" srcdir="${test.source.dir}" classpathref="build.classpath" />
</target>
<target name="javadoc" depends="init">
<javadoc author="true" charset="gbk" classpathref="build.classpath"
destdir="${javadoc.dir}" version="true" use="true" sourcepath="${source.dir}"></javadoc>
</target>
<target name="test" depends="compile-tests" description="runs JUnit tests">
<mkdir dir="${test.report.dir}" />
<junit dir="${basedir}" printSummary="on" fork="true" haltonfailure="true">
<sysproperty key="basedir" value="${basedir}" />
<formatter type="xml" />
<classpath>
<path refid="build.classpath" />
<pathelement path="${test.classes.dir}" />
<pathelement path="${classes.dir}" />
</classpath>
<batchtest todir="${test.report.dir}">
<fileset dir="${test.source.dir}">
<include name="${test.pattern}" />
</fileset>
</batchtest>
</junit>
</target>
<target name="coverage-check">
<cobertura-check branchrate="40" totallinerate="100" />
</target>
<target name="coverage-report">
<cobertura-report srcdir="${source.dir}" destdir="${cobertura.dir}" format="html" />
</target>
<target name="alternate-coverage-report">
<!--
Generate a series of HTML files containing the coverage
data in a user-readable form using nested source filesets.
-->
<cobertura-report destdir="${cobertura.dir}">
<fileset dir="${source.dir}">
<include name="**/*.java"/>
</fileset>
</cobertura-report>
</target>
<target name="coverage" depends="jar,instrument,test,coverage-report,alternate-coverage-report"/>
<target name="pmd" depends="test">
<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpathref="build.classpath"/>
<pmd>
<ruleset>rulesets/basic.xml</ruleset>
<ruleset>rulesets/braces.xml</ruleset>
<ruleset>rulesets/javabeans.xml</ruleset>
<ruleset>rulesets/unusedcode.xml</ruleset>
<ruleset>rulesets/strings.xml</ruleset>
<ruleset>rulesets/design.xml</ruleset>
<ruleset>rulesets/coupling.xml</ruleset>
<ruleset>rulesets/codesize.xml</ruleset>
<ruleset>rulesets/imports.xml</ruleset>
<ruleset>rulesets/naming.xml</ruleset>
<formatter type="xml" toFile="${default.target.dir}/pmd_report.xml" />
<fileset dir="${source.dir}">
<include name="**/*.java" />
</fileset>
</pmd>
</target>
<target name="findbugs" depends="jar">
<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"
classpathref="build.classpath" />
<findbugs classpathref="build.classpath" pluginlist="${lib.dir}/coreplugin-1.0.jar"
output="xml" outputFile="${default.target.dir}/findbugs.xml">
<sourcePath path="${source.dir}" />
<class location="${default.target.dir}/${ant.project.name}.jar" />
</findbugs>
</target>
<target name="all" depends="coverage,pmd,findbugs,javadoc" />
</project>
从上面的配置我们看到这个构建包括:编译、测试、测试覆盖率统计、代码检查、BUG查找、生成Javadoc和打包。
Maven的配置pom.xml
Maven详细的站点生成可以参考这里:http://www.duduwolf.com/wiki/2008/766.html
Maven生成的站点例子:
CC生成的集成报告截图如下:
四、总结篇:
通过上面的简单介绍,我们基本掌握了持续集成的目的和基本理论,在Martin Fowler的文章中提到了一些最佳实践也值得参考。当然持续集成是一个在实践中不断发展和完善的过程,对于一个团队而言,引入持续集成对于提高开发效率和规范开发过程是必需的,不过在整个持续集成中,我们信赖的依据就是构建,其中的单元测试可靠性就会有一定的要求,这样对于我们开发人员,如何保证写出高质量的单元测试便是一个挑战,TDD是一个不错的实践,它完全从需求出发,逐步完善测试用例,不断减少与需求的偏差来尽量满足需求。同时引入测试覆盖率也利于我们审查我们的单元测试。CC提供的统一出口的各种报告和图表,可以更加直观和快捷的从整体上把握我们代码在构建中表现出来的健壮性(代码检查)和满足需求性(单元测试通过率、测试覆盖率),同时对于出现的问题,能够责任到人,快速反馈也是很有利于问题的解决,对于持续集成的学习刚刚开始,错误偏颇在所难免,越是深入的学习,越会有更多的感悟和思考。
五、参考:
1、Martin Fowler的文章
原文:http://martinfowler.com/articles/continuousIntegration.html
翻译:http://dev.csdn.net/develop/article/12/12286.shtm
2、Juven的一篇原创文章
http://juvenshun.spaces.live.com/blog/cns!CF7D1BC903C111E1!284.entry
3、IBM DW上的一篇教程,以Hudson为例
https://www6.software.ibm.com/developerworks/cn/education/java/j-cq11207/index.html
4、满江红开源上提供的CruiseControl教程下载
http://www.xiaxin.net/blog/OpenDoc-CruiseControl.zip
5、IBM DW上的另外一篇文章讲解如何实现持续集成
http://www.ibm.com/developerworks/cn/rational/rationaledge/content/nov05/lee/
持续集成鼓励尽量短周期内项目团队的代码提交,同时保证每次check in都不会损害我们的构建通过。它跟每日构建的区别就在于代码提交频率更高(一般为一个小时),构建的频率也更高,这样做的目的就是为了快速反馈,使得BUG越早被发现,并能以邮件或者消息(甚至短信)的形式快速反馈给开发人员,从而快速解决问题,并保证构建成功。
二、工具篇:
持续集成重在COC(Conversion Over Configuration:约定由于配置),这样选择合适的支持持续集成的工具就相当重要。庆幸的是我们有许多开源的选择,但是首先我们需要了解持续集成的实现架构:
从上图中我们看到,客户端提交代码更改到源代码仓库,CI服务器会检测到代码库的修改,它会检出代码,在本地构建,构建成功,会将构建结果反馈回客户端,同时可能将构建的可运行代码发布到WEB服务器上。
所以,我们就需要各个节点的工具支持:
对于SCM工具,我们的选择的开源工具有CVS、SVN等,这也没有特殊的取舍,就自己的爱好和公司的已有平台而定。我们这里假设使用SVN作为版本管理工具,它的中文站是:http://www.subversion.org.cn/ ;
对于构建工具,我们的选择的开源工具有Ant和Maven等,Ant通过一些内置的和扩展的Task来实现包括文件操作、编译、测试、代码检查、打包等操作,Eclipse默认提供了对Ant的支持。通过在build.xml中配置一系列相互依赖的任务来实现我们定义的构建过程。Maven是一个以项目为模型的构建,项目管理工具,注意它并不是为了替代Ant(同时支持运行Ant脚本),而是以另一种视觉提供了对软件生命周期的管理,它通过插件的方式提供了类似于Ant任务的功能,它的特色之处在于对项目依赖组件的统一管理,同时它的生成站点功能也是一个不错的特性,具体不再赘述,后面的构建我们会分别用Ant和Maven来说明。
对于持续集成工具,我们的选择的开源工具有CruiseControl(后面简称CC)和Hudson。BuildLoop是CC的核心, 这个BuildLoop包含插件支持,详细介绍可以参照附件中的电子书。CC的实施结构如下图所示:
为了完成上面的结构,CC提供的插件按照下图所示的流程完成构建:
通过对CC的cruisecontrol项目的配置,支持图形化显示checkstyle, pmd, findbugs, cobertura, javadoc等报告。同时CC-Config也提供了对项目的图形化配置,比较方便。
Hudson也提供了持续集成服务器的大多数功能,详细参考官方站点:https://hudson.dev.java.net/
三、实践篇:
我们模拟了两个项目,一个AntBasedCI是基于Ant构建的客户端应用程序,一个MavenBasedCI是基于Maven构建的Web应用程序,我们的SCM由SVN自带的Server提供,启动这个服务,我们需要在命令行运行: svnserve -d -r D:/repos 其中-r指定repository磁盘位置。
CC的项目配置:
Xml代码
<cruisecontrol>
<project requiremodification="false" forceonly="false" name="MavenBasedCI">
<modificationset QuietPeriod="30">
<svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" />
</modificationset>
<schedule Interval="300">
<maven2 Goal="-e clean site install" MvnHome="D:/OpenSource/maven-2.0.4" PomFile="${checkout.dir}/${project.name}/pom.xml" ShowProgress="false" />
</schedule>
<bootstrappers>
<svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" />
</listeners>
<log>
<merge Dir="${logs.dir}/${project.name}" />
<merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" />
</log>
<publishers>
<onsuccess>
<artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" />
</onsuccess>
<artifactspublisher File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" Dest="artifacts/${project.name}" />
<artifactspublisher Dir="${checkout.dir}/${project.name}/target/site" Dest="artifacts/${project.name}" />
</publishers>
<property name="checkout.dir" value="${basedir}/checkout" />
<property name="logs.dir" value="${basedir}/logs" />
<property name="artifact.dir" value="${basedir}/artifacts" />
</project>
<project requiremodification="false" forceonly="false" name="AntBasedCI">
<modificationset QuietPeriod="30">
<svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" />
</modificationset>
<schedule Interval="300">
<ant AntHome="D:/OpenSource/apache-ant-1.7.1" BuildFile="${checkout.dir}/${project.name}/build.xml" Target="all" />
</schedule>
<bootstrappers>
<svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" />
</bootstrappers>
<listeners>
<currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" />
</listeners>
<log>
<merge Dir="${logs.dir}/${project.name}" />
<merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" />
</log>
<publishers>
<onsuccess>
<artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" />
</onsuccess>
<artifactspublisher Dest="artifacts/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" />
<artifactspublisher Dir="${checkout.dir}/${project.name}/target" Dest="artifacts/${project.name}" />
</publishers>
</project>
<dashboard />
<property name="basedir" value="E:/CI/ccworkspace" />
<property name="checkout.dir" value="${basedir}/checkout" />
<property name="logs.dir" value="${basedir}/logs" />
<property name="artifact.dir" value="${basedir}/artifacts" />
</cruisecontrol>
<cruisecontrol> <project requiremodification="false" forceonly="false" name="MavenBasedCI"> <modificationset QuietPeriod="30"> <svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" /> </modificationset> <schedule Interval="300"> <maven2 Goal="-e clean site install" MvnHome="D:/OpenSource/maven-2.0.4" PomFile="${checkout.dir}/${project.name}/pom.xml" ShowProgress="false" /> </schedule> <bootstrappers> <svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" /> </bootstrappers> <listeners> <currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" /> </listeners> <log> <merge Dir="${logs.dir}/${project.name}" /> <merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" /> </log> <publishers> <onsuccess> <artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" /> </onsuccess> <artifactspublisher File="${checkout.dir}/${project.name}/target/${project.name}-1.0-SNAPSHOT.war" Dest="artifacts/${project.name}" /> <artifactspublisher Dir="${checkout.dir}/${project.name}/target/site" Dest="artifacts/${project.name}" /> </publishers> <property name="checkout.dir" value="${basedir}/checkout" /> <property name="logs.dir" value="${basedir}/logs" /> <property name="artifact.dir" value="${basedir}/artifacts" /> </project> <project requiremodification="false" forceonly="false" name="AntBasedCI"> <modificationset QuietPeriod="30"> <svn LocalWorkingCopy="${checkout.dir}/${project.name}" CheckExternals="false" UseLocalRevision="false" /> </modificationset> <schedule Interval="300"> <ant AntHome="D:/OpenSource/apache-ant-1.7.1" BuildFile="${checkout.dir}/${project.name}/build.xml" Target="all" /> </schedule> <bootstrappers> <svnbootstrapper LocalWorkingCopy="${checkout.dir}/${project.name}" /> </bootstrappers> <listeners> <currentbuildstatuslistener File="${logs.dir}/${project.name}/status.txt" /> </listeners> <log> <merge Dir="${logs.dir}/${project.name}" /> <merge Dir="${checkout.dir}/${project.name}/target" Pattern="*.xml" /> </log> <publishers> <onsuccess> <artifactspublisher Dest="${artifact.dir}/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" /> </onsuccess> <artifactspublisher Dest="artifacts/${project.name}" File="${checkout.dir}/${project.name}/target/${project.name}.jar" /> <artifactspublisher Dir="${checkout.dir}/${project.name}/target" Dest="artifacts/${project.name}" /> </publishers> </project> <dashboard /> <property name="basedir" value="E:/CI/ccworkspace" /> <property name="checkout.dir" value="${basedir}/checkout" /> <property name="logs.dir" value="${basedir}/logs" /> <property name="artifact.dir" value="${basedir}/artifacts" /> </cruisecontrol>
上面的配置是通过CC-Config图形配置自动生成的,包含我们的两个工程。
Ant配置build.xml
Xml代码
<?xml version="1.0" encoding="UTF-8"?>
<project name="AntBasedCI" default="all">
<property name="default.target.dir" value="target" />
<property name="classes.dir" value="${default.target.dir}/classes" />
<property name="test.classes.dir" value="${default.target.dir}/test-classes" />
<property name="test.report.dir" value="${default.target.dir}/test-reports" />
<property name="lib.dir" value="${basedir}/lib" />
<property name="javadoc.dir" value="${default.target.dir}/apidocs" />
<property name="source.dir" value="src" />
<property name="test.source.dir" value="test" />
<property name="test.pattern" value="**/**Test.java" />
<!-- Coverage reports are deposited into these directories -->
<property name="cobertura.dir" value="${default.target.dir}/cobertura"/>
<!-- Instrumented classes are deposited into this directory -->
<property name="instrumented.dir" value="instrumented" />
<path id="cobertura.classpath">
<fileset dir="${lib.dir}">
<include name="*.jar" />
</fileset>
</path>
<taskdef classpathref="cobertura.classpath" resource="tasks.properties"/>
<target name="clean">
<delete dir="${classes.dir}"/>
<delete dir="${test.classes.dir}"/>
<delete dir="${default.target.dir}"/>
</target>
<target name="init" depends="clean">
<mkdir dir="${classes.dir}" />
<mkdir dir="${test.classes.dir}" />
<mkdir dir="${javadoc.dir}" />
<mkdir dir="${default.target.dir}"/>
<mkdir dir="${instrumented.dir}"/>
<path id="build.classpath">
<fileset dir="${lib.dir}">
<include name="**/*.jar" />
</fileset>
<fileset dir="${default.target.dir}">
<include name="**/*.jar" />
</fileset>
</path>
</target>
<target name="compile-source" depends="init" description="compiles all .java files in source directory ">
<javac destdir="${classes.dir}" srcdir="${source.dir}" classpathref="build.classpath" />
</target>
<target name="instrument" depends="compile-source">
<delete file="cobertura.ser"/>
<delete dir="${instrumented.dir}" />
<!--Instrument the application classes, writing the instrumented classes into ${build.instrumented.dir}.-->
<cobertura-instrument todir="${instrumented.dir}">
<ignore regex="org.apache.log4j.*" />
<fileset dir="${classes.dir}">
<!-- Instrument all the application classes, but don't instrument the test classes.-->
<include name="**/*.class" />
<exclude name="**/*Test.class" />
</fileset>
</cobertura-instrument>
</target>
<target name="jar" depends="instrument">
<jar jarfile="${default.target.dir}/${ant.project.name}.jar" basedir="${classes.dir}" />
</target>
<target name="compile-tests" depends="jar" description="compiles all .java files in test directory ">
<javac destdir="${test.classes.dir}" srcdir="${test.source.dir}" classpathref="build.classpath" />
</target>
<target name="javadoc" depends="init">
<javadoc author="true" charset="gbk" classpathref="build.classpath"
destdir="${javadoc.dir}" version="true" use="true" sourcepath="${source.dir}"></javadoc>
</target>
<target name="test" depends="compile-tests" description="runs JUnit tests">
<mkdir dir="${test.report.dir}" />
<junit dir="${basedir}" printSummary="on" fork="true" haltonfailure="true">
<sysproperty key="basedir" value="${basedir}" />
<formatter type="xml" />
<classpath>
<path refid="build.classpath" />
<pathelement path="${test.classes.dir}" />
<pathelement path="${classes.dir}" />
</classpath>
<batchtest todir="${test.report.dir}">
<fileset dir="${test.source.dir}">
<include name="${test.pattern}" />
</fileset>
</batchtest>
</junit>
</target>
<target name="coverage-check">
<cobertura-check branchrate="40" totallinerate="100" />
</target>
<target name="coverage-report">
<cobertura-report srcdir="${source.dir}" destdir="${cobertura.dir}" format="html" />
</target>
<target name="alternate-coverage-report">
<!--
Generate a series of HTML files containing the coverage
data in a user-readable form using nested source filesets.
-->
<cobertura-report destdir="${cobertura.dir}">
<fileset dir="${source.dir}">
<include name="**/*.java"/>
</fileset>
</cobertura-report>
</target>
<target name="coverage" depends="jar,instrument,test,coverage-report,alternate-coverage-report"/>
<target name="pmd" depends="test">
<taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpathref="build.classpath"/>
<pmd>
<ruleset>rulesets/basic.xml</ruleset>
<ruleset>rulesets/braces.xml</ruleset>
<ruleset>rulesets/javabeans.xml</ruleset>
<ruleset>rulesets/unusedcode.xml</ruleset>
<ruleset>rulesets/strings.xml</ruleset>
<ruleset>rulesets/design.xml</ruleset>
<ruleset>rulesets/coupling.xml</ruleset>
<ruleset>rulesets/codesize.xml</ruleset>
<ruleset>rulesets/imports.xml</ruleset>
<ruleset>rulesets/naming.xml</ruleset>
<formatter type="xml" toFile="${default.target.dir}/pmd_report.xml" />
<fileset dir="${source.dir}">
<include name="**/*.java" />
</fileset>
</pmd>
</target>
<target name="findbugs" depends="jar">
<taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"
classpathref="build.classpath" />
<findbugs classpathref="build.classpath" pluginlist="${lib.dir}/coreplugin-1.0.jar"
output="xml" outputFile="${default.target.dir}/findbugs.xml">
<sourcePath path="${source.dir}" />
<class location="${default.target.dir}/${ant.project.name}.jar" />
</findbugs>
</target>
<target name="all" depends="coverage,pmd,findbugs,javadoc" />
</project>
<?xml version="1.0" encoding="UTF-8"?> <project name="AntBasedCI" default="all"> <property name="default.target.dir" value="target" /> <property name="classes.dir" value="${default.target.dir}/classes" /> <property name="test.classes.dir" value="${default.target.dir}/test-classes" /> <property name="test.report.dir" value="${default.target.dir}/test-reports" /> <property name="lib.dir" value="${basedir}/lib" /> <property name="javadoc.dir" value="${default.target.dir}/apidocs" /> <property name="source.dir" value="src" /> <property name="test.source.dir" value="test" /> <property name="test.pattern" value="**/**Test.java" /> <!-- Coverage reports are deposited into these directories --> <property name="cobertura.dir" value="${default.target.dir}/cobertura"/> <!-- Instrumented classes are deposited into this directory --> <property name="instrumented.dir" value="instrumented" /> <path id="cobertura.classpath"> <fileset dir="${lib.dir}"> <include name="*.jar" /> </fileset> </path> <taskdef classpathref="cobertura.classpath" resource="tasks.properties"/> <target name="clean"> <delete dir="${classes.dir}"/> <delete dir="${test.classes.dir}"/> <delete dir="${default.target.dir}"/> </target> <target name="init" depends="clean"> <mkdir dir="${classes.dir}" /> <mkdir dir="${test.classes.dir}" /> <mkdir dir="${javadoc.dir}" /> <mkdir dir="${default.target.dir}"/> <mkdir dir="${instrumented.dir}"/> <path id="build.classpath"> <fileset dir="${lib.dir}"> <include name="**/*.jar" /> </fileset> <fileset dir="${default.target.dir}"> <include name="**/*.jar" /> </fileset> </path> </target> <target name="compile-source" depends="init" description="compiles all .java files in source directory "> <javac destdir="${classes.dir}" srcdir="${source.dir}" classpathref="build.classpath" /> </target> <target name="instrument" depends="compile-source"> <delete file="cobertura.ser"/> <delete dir="${instrumented.dir}" /> <!--Instrument the application classes, writing the instrumented classes into ${build.instrumented.dir}.--> <cobertura-instrument todir="${instrumented.dir}"> <ignore regex="org.apache.log4j.*" /> <fileset dir="${classes.dir}"> <!-- Instrument all the application classes, but don't instrument the test classes.--> <include name="**/*.class" /> <exclude name="**/*Test.class" /> </fileset> </cobertura-instrument> </target> <target name="jar" depends="instrument"> <jar jarfile="${default.target.dir}/${ant.project.name}.jar" basedir="${classes.dir}" /> </target> <target name="compile-tests" depends="jar" description="compiles all .java files in test directory "> <javac destdir="${test.classes.dir}" srcdir="${test.source.dir}" classpathref="build.classpath" /> </target> <target name="javadoc" depends="init"> <javadoc author="true" charset="gbk" classpathref="build.classpath" destdir="${javadoc.dir}" version="true" use="true" sourcepath="${source.dir}"></javadoc> </target> <target name="test" depends="compile-tests" description="runs JUnit tests"> <mkdir dir="${test.report.dir}" /> <junit dir="${basedir}" printSummary="on" fork="true" haltonfailure="true"> <sysproperty key="basedir" value="${basedir}" /> <formatter type="xml" /> <classpath> <path refid="build.classpath" /> <pathelement path="${test.classes.dir}" /> <pathelement path="${classes.dir}" /> </classpath> <batchtest todir="${test.report.dir}"> <fileset dir="${test.source.dir}"> <include name="${test.pattern}" /> </fileset> </batchtest> </junit> </target> <target name="coverage-check"> <cobertura-check branchrate="40" totallinerate="100" /> </target> <target name="coverage-report"> <cobertura-report srcdir="${source.dir}" destdir="${cobertura.dir}" format="html" /> </target> <target name="alternate-coverage-report"> <!-- Generate a series of HTML files containing the coverage data in a user-readable form using nested source filesets. --> <cobertura-report destdir="${cobertura.dir}"> <fileset dir="${source.dir}"> <include name="**/*.java"/> </fileset> </cobertura-report> </target> <target name="coverage" depends="jar,instrument,test,coverage-report,alternate-coverage-report"/> <target name="pmd" depends="test"> <taskdef name="pmd" classname="net.sourceforge.pmd.ant.PMDTask" classpathref="build.classpath"/> <pmd> <ruleset>rulesets/basic.xml</ruleset> <ruleset>rulesets/braces.xml</ruleset> <ruleset>rulesets/javabeans.xml</ruleset> <ruleset>rulesets/unusedcode.xml</ruleset> <ruleset>rulesets/strings.xml</ruleset> <ruleset>rulesets/design.xml</ruleset> <ruleset>rulesets/coupling.xml</ruleset> <ruleset>rulesets/codesize.xml</ruleset> <ruleset>rulesets/imports.xml</ruleset> <ruleset>rulesets/naming.xml</ruleset> <formatter type="xml" toFile="${default.target.dir}/pmd_report.xml" /> <fileset dir="${source.dir}"> <include name="**/*.java" /> </fileset> </pmd> </target> <target name="findbugs" depends="jar"> <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask" classpathref="build.classpath" /> <findbugs classpathref="build.classpath" pluginlist="${lib.dir}/coreplugin-1.0.jar" output="xml" outputFile="${default.target.dir}/findbugs.xml"> <sourcePath path="${source.dir}" /> <class location="${default.target.dir}/${ant.project.name}.jar" /> </findbugs> </target> <target name="all" depends="coverage,pmd,findbugs,javadoc" /> </project>
从上面的配置我们看到这个构建包括:编译、测试、测试覆盖率统计、代码检查、BUG查找、生成Javadoc和打包。
Maven的配置pom.xml
<?xml version="1.0" encoding="GBK"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.samueli.webapp</groupId> <artifactId>MavenBasedCI</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>MavenBasedCI</name> <description>持续集成测试项目</description> <url>http://www.samueli.com</url> <licenses> <license> <name>Fake License</name> <url>http://127.0.0.1:8080/dc-license.txt</url> <distribution>repo</distribution> </license> </licenses> <!-- 问题管理 --> <issueManagement> <system>jira</system> <url>http://169.254.11.166:8088/secure/Dashboard.jspa</url> </issueManagement> <!-- 源代码管理 --> <scm> <connection>scm:svn://127.0.0.1/repos/MavenBasedCI</connection> <developerConnection> scm:svn://127.0.0.1/repos/MavenBasedCI </developerConnection> <tag>head</tag> <url>svn://127.0.0.1/repos/MavenBasedCI</url> </scm> <!-- 开发人员以及角色定义 --> <developers> <developer> <name>aaa</name> <id>aaa</id> <email>aaa@samueli.com</email> <organization>samueli</organization> <roles> <role>PM</role> </roles> </developer> <developer> <name>bbb</name> <id>bbb</id> <email>bbb@samueli.com</email> <organization>samueli</organization> <roles> <role>设计人员</role> <role>开发人员</role> </roles> </developer> </developers> <!-- 依赖定义 --> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.3</version> </dependency> </dependencies> <!--依赖版本管理--> <dependencyManagement /> <organization> <name>XXX有限责任公司</name> <url>http://www.samueli.com</url> </organization> <!--项目模块管理--> <modules /> <build> <plugins> <plugin> <artifactId>maven-site-plugin</artifactId> <configuration> <locales>zh_CN</locales> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> <encoding>GBK</encoding> </configuration> </plugin> <plugin> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <executions> <execution> <goals> <goal>check</goal> </goals> </execution> </executions> <configuration> <linkXref>true</linkXref> <sourceEncoding>GBK</sourceEncoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.0</version> <configuration> <instrumentation> <excludes> <exclude> com/samueli/MavenBasedCI/util/*.class </exclude> </excludes> </instrumentation> <check> <haltOnFailure>true</haltOnFailure> <totalLineRate>40</totalLineRate> <totalBranchRate>100</totalBranchRate> </check> <outputEncoding>GBK</outputEncoding> </configuration> <executions> <execution> <id>clean</id> <goals> <goal>clean</goal> </goals> </execution> <execution> <id>check</id> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> </plugins> <extensions> <extension> <groupId>org.apache.maven.wagon</groupId> <artifactId>wagon-ftp</artifactId> <version>1.0-alpha-6</version> </extension> </extensions> </build> <distributionManagement> <repository> <id>ftp-repository</id> <url>ftp://192.168.3.241</url> </repository> <site> <id>lmss.site</id> <name>MavenBasedCI</name> <url>file:///Y:/</url> </site> </distributionManagement> <!--各种报告--> <reporting> <plugins> <plugin> <artifactId>maven-site-plugin</artifactId> <configuration> <locales>zh_CN</locales> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <artifactId>maven-surefire-report-plugin</artifactId> <configuration> <showSuccess>false</showSuccess> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jxr-plugin</artifactId> <configuration> <outputEncoding>GBK</outputEncoding> <inputEncoding>GBK</inputEncoding> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <configuration> <aggregate>true</aggregate> <charset>UTF16</charset> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <configuration> <rulesets> <ruleset>/rulesets/basic.xml</ruleset> <ruleset>/rulesets/imports.xml</ruleset> <ruleset>/rulesets/unusedcode.xml</ruleset> <ruleset>/rulesets/finalizers.xml</ruleset> </rulesets> <outputEncoding>GBK</outputEncoding> <linkXref>true</linkXref> <excludes> <exclude> com/samueli/MavenBasedCI/util/*.java </exclude> </excludes> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <configLocation>lmss_checks.xml</configLocation> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>taglist-maven-plugin</artifactId> <configuration> <tags> <tag>TODO</tag> <tag>@todo</tag> <tag>FIXME</tag> <tag>XXX</tag> </tags> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> <version>2.0</version> <configuration> <outputEncoding>GBK</outputEncoding> </configuration> </plugin> <!-- <plugin>--> <!-- <groupId>org.codehaus.mojo</groupId>--> <!-- <artifactId>changes-maven-plugin</artifactId>--> <!-- </plugin>--> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>changelog-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <configuration> <xmlOutput>true</xmlOutput> </configuration> </plugin> </plugins> </reporting> </project>
Maven详细的站点生成可以参考这里:http://www.duduwolf.com/wiki/2008/766.html
Maven生成的站点例子:
CC生成的集成报告截图如下:
四、总结篇:
通过上面的简单介绍,我们基本掌握了持续集成的目的和基本理论,在Martin Fowler的文章中提到了一些最佳实践也值得参考。当然持续集成是一个在实践中不断发展和完善的过程,对于一个团队而言,引入持续集成对于提高开发效率和规范开发过程是必需的,不过在整个持续集成中,我们信赖的依据就是构建,其中的单元测试可靠性就会有一定的要求,这样对于我们开发人员,如何保证写出高质量的单元测试便是一个挑战,TDD是一个不错的实践,它完全从需求出发,逐步完善测试用例,不断减少与需求的偏差来尽量满足需求。同时引入测试覆盖率也利于我们审查我们的单元测试。CC提供的统一出口的各种报告和图表,可以更加直观和快捷的从整体上把握我们代码在构建中表现出来的健壮性(代码检查)和满足需求性(单元测试通过率、测试覆盖率),同时对于出现的问题,能够责任到人,快速反馈也是很有利于问题的解决,对于持续集成的学习刚刚开始,错误偏颇在所难免,越是深入的学习,越会有更多的感悟和思考。
五、参考:
1、Martin Fowler的文章
原文:http://martinfowler.com/articles/continuousIntegration.html
翻译:http://dev.csdn.net/develop/article/12/12286.shtm
2、Juven的一篇原创文章
http://juvenshun.spaces.live.com/blog/cns!CF7D1BC903C111E1!284.entry
3、IBM DW上的一篇教程,以Hudson为例
https://www6.software.ibm.com/developerworks/cn/education/java/j-cq11207/index.html
4、满江红开源上提供的CruiseControl教程下载
http://www.xiaxin.net/blog/OpenDoc-CruiseControl.zip
5、IBM DW上的另外一篇文章讲解如何实现持续集成
http://www.ibm.com/developerworks/cn/rational/rationaledge/content/nov05/lee/
相关文章推荐
- 用Spring MVC3 + Ant + Jenkins + SVN + Tomcat 做一个简单的持续集成例子
- Jenkins——如何快速搭建一个简单的基于 Jenkins 的持续集成环境
- Web Services开发总结三 一个简单的XML格式定义
- 对 Softirq,tasklet和workqueue做一个简单的总结和分析
- 持续集成相关总结(五)
- Jenkins学习总结(4)——持续集成,持续交付,持续部署之间的区别
- Jenkins持续集成实战总结
- CentOS 7 简单命令总结一(随着使用持续更新)
- jenkins持续集成环境搭建拦路虎和vc6.0编译莫名停止故障解决方法总结
- 我认为比较容易理解的一个案例:利用Xinc+phing+phpunit+svn在linux上构建持续集成平台
- 【ROS总结】教程Actionlib——使用Execute Callback编写一个简单的行为服务器
- (6)MyBatis小总结:一个简单的例子--接口编程改进
- 刚学会百度地图最新版的sdk,总结一个简单的demo
- 使用flume问题总结3——一个使用flume拦截器和选择器的简单实例
- 转一个后缀数组的简单总结:
- Bitnami LNMP集成包安装简单总结
- 一个集成输入查找功能的的java简单程序
- 简单介绍Jenkins&持续集成
- Jenkins学习总结(4)——持续集成,持续交付,持续部署之间的区别
- LZH_IJKPlayer-一个最简单使用的视频播放器,集成于bilibili开源直播播放器(ijkplayer)