敏捷开发的一个基础就是使用自动化的构建过程,而这又是依赖于构建脚本工具的。ant是一个基于java的优秀的构建工具,我相信网上关于ant的介绍是铺天盖地,而ant的使用方法最好就是看ant的手册,这里我想将的是ant的实际用途,千万不要为了用ant而用ant,工具的作用在于提高你的效率。
首先是这样一个场景,在我去年用eclipse开发web application的时候,wtp还没有成熟,一直用lomboz,lomboz也是个不错的插件,但问题在于,在快速开发方面没有什么支持?什么是快速开发?我的理解是,所谓快速开发,是要保证开发人员对于开发环境和部署环境的区别是透明的,说的通俗点,我一旦改好代码,希望按个按钮就能把改好的程序跑起来马上测试一下(这方面wtp做的非常棒),但是对于web application跑一个用需要一个部署过程,还要起tomcat这里的容器。也就是说,我一旦改好代码,想测试一下的话,要做这么几件事情,首先进入开发的目录,然后确保是一个正确的web application的目录结构(这一点lomboz已经帮你做好了),然后把整个目录部署在容器中(其实可以修改容器的配置文件,把应用的位置指向开发环境,但是当时我还是小白,不知道可以这样,而且这会打乱容器的正常部署),最后启动容器。
简直是太麻烦了!于是网上找了个tomcat的插件,这样就可以在eclipse中点击一下按钮,就把容器起来了,不用到控制台打命令。但这仅仅解决了最后一步,上面怎么办?
用ant解决!写一个部署脚本,完成上面的步骤:
<project name="deploy" default="all" basedir="." >
<property name="dest.dir" value="C:/Program Files/Apache Software Foundation/Tomcat 5.5
/webapps/AjaxSamples"></property>
<property name="class.dir" value="build/classes"></property>
<target name="copyClass">
<copy todir="${dest.dir}/WEB-INF/classes">
<fileset dir="${class.dir}">
<include name="**/*"/>
</fileset>
</copy>
</target>
<target name="copyOther">
<copy todir="${dest.dir}">
<fileset dir="WebContent">
<include name="**/*"/>
</fileset>
</copy>
</target>
<target name="all" depends="copyClass,copyOther">
</target>
</project>
这个project有两个target,第一个是部署class字节码文件,第二个是部署其他文件。分成两个的原因是因为在开发环境,字节码文件有另外的输出目录。dest.dir是tomcat的部署目录,class.dir是开发环境字节码的输出目录,这样一旦改完代码,只需要做两件事情,第一,运行一下脚本,第二,启动tomcat,然后就能直接在浏览器测试了。而且tomcat支持热插拔,甚至不用重启tomcat,只要运行脚本即可。
上述这个例子比较简单,而且似乎和敏捷开发没什么帮助,下面给个稍微复杂的例子,在开源社区有许多帮助完成持续集成(continous integration)的工具,比如cruisecontrol,luntbuild等,这些构建的运行是基于用户自己的构建脚本的。下面我就举个这样的ant脚本的例子。写分析一下持续集成应该做哪些任务。第一,从cvs上下载源码,第二编译源码,第三编译测试代码,第四运行测试案例(junit),第五输出测试报告,第六部署报告。这里我要加一个步骤,持续集成经常要查看一些进一步的测试情况,这里假定我们会用emma这个优秀的分析代码覆盖率的工具,所以我要加的步骤就是在编译完源码之后需要往源码的字节码注入emma内容的一个target,和最后输出并部署emma报告的target并在原来的做 junit的target中加入emma的jvm参数。下面是整个脚本:
<project name="buildWithEmma" default="execute">
<target name="init">
<property name="src.dir" value="src"></property>
<property name="class.dir" value="bin"></property>
<property name="testsrc.dir" value="test"></property>
<property name="testclass.dir" value="testBin"></property>
<property name="lib.dir" value="lib"></property>
<property name="reports.dir" value="reports"></property>
<property name="junitReport.dir" value="${reports.dir}/junit"></property>
<property name="bin.instr.dir" value="instrBin"></property>
<property name="coverage.dir" value="coverage"></property>
<property name="emma.enabled" value="true"></property>
</property>
<path id="class.main.lib">
<pathelement location="${class.dir}"/>
<fileset dir="${lib.dir}">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="class.src">
<pathelement location="${class.dir}"/>
</path>
<path id ="emma.lib">
<pathelement location="${lib.dir}/emma.jar"/>
<pathelement location="${lib.dir}/emma_ant.jar"/>
</path>
<taskdef resource="emma_ant.properties" classpathref="emma.lib" />
</target>
<target name="compileSrc" depends="init">
<delete dir="${class.dir}"></delete>
<mkdir dir="${class.dir}"> </mkdir>
<javac srcdir="${src.dir}"
destdir="${class.dir}"
debug="on"
>
<classpath refid="class.main.lib"/>
</javac>
<copy todir="${class.dir}">
<fileset dir="${src.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="compileTest" depends="init">
<delete dir="${testclass.dir}"></delete>
<mkdir dir="${testclass.dir}"/>
<javac srcdir="${testsrc.dir}"
destdir="${testclass.dir}"
debug="on"
>
<classpath refid="class.main.lib" />
</javac>
<copy todir="${class.dir}">
<fileset dir="${src.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="instrument" depends="compileSrc">
<delete dir="${bin.instr.dir}"></delete>
<delete dir="${coverage.dir}"></delete>
<mkdir dir="${bin.instr.dir}" />
<mkdir dir="${coverage.dir}" />
<emma enabled="${emma.enabled}">
<instr instrpathref="class.src"
destdir="${bin.instr.dir}"
metadatafile="${coverage.dir}/metadata.emma"
merge="true">
</instr>
</emma>
<copy todir="${bin.instr.dir}">
<fileset dir="${class.dir}">
<exclude name="**/*.java" />
</fileset>
</copy>
</target>
<target name="test" depends="compileTest">
<delete dir="${junitReport.dir}" failοnerrοr="false"/>
<mkdir dir="${junitReport.dir}"/>
<junit fork="true" forkmode="once" printsummary="withOutAndErr"
errorproperty="test.error" showoutput="on">
<!--Refer to metadata.emma to collect run information-->
<jvmarg value="-Demma.coverage.out.file=${coverage.dir}/coverage.emma"/>
<jvmarg value="-Demma.coverage.out.merge=false" />
<!--Test support package-->
<classpath location="${bin.instr.dir}" />
<classpath location="${testclass.dir}" />
<classpath refid="emma.lib" />
<formatter type="xml" />
<!--Batch test exclude inner class-->
<batchtest todir="${junitReport.dir}" haltonfailure="no">
<fileset dir="${testclass.dir}">
<include name="**/*Test.class" />
</fileset>
</batchtest>
</junit>
</target>
<target name="junitReport" depends="test">
<junitreport todir="${junitReport.dir}">
<fileset dir="${junitReport.dir}">
<include name="*" />
</fileset>
<report format="frames" todir="${junitReport.dir}" />
</junitreport>
</target>
<target name="coverageReport" depends="test">
<emma enabled="${emma.enabled}">
<report sourcepath="${src.dir}"
sort="+block,+name,+method,+class"
metrics="method:70,block:80,line:80,class:100">
<fileset dir="${coverage.dir}">
<include name="*.emma" />
</fileset>
<html outfile="${coverage.dir}/coverage.html"
depth="method" columns="name,class,method,block,line" />
</report>
</emma>
</target>
<target name="execute"
depends="init,compileSrc,instrument,compileTest,test,junitReport,coverageReport">
</target>
</project>
由于部署junit报告和emma报告的过程和之前的一个例子相似,所以给出的脚本就不包含这两个部署过程了。关于emma的一些特定ant的任务,请大家自行查阅相关资料,本文的目的还是在于关注ant脚本的整个流程,如何让脚本帮助进行快速开发。事实上即时没有持续集成工具,这样的一个脚本已经实现了大部分的自动构建功能了。完全可以手动选择时间来运行这个脚本以构建完整的系统。
最后我想说的是,ant是帮助你提高效率的,我们需要关注的不是ant如何使用,而是何时使用。你是否在你的琐碎的日常工作中想到用 ant来帮助你提高效率呢?