Summary: A Sample Cobertura Ant build script.
I still haven't gotten around to writing a Cobertura code-coverage tutorial, but in lieu of that, I thought I'd include an ant build script here that does a lot of powerful things, including a task that generates Cobertura code-coverage reports.
If you've never seen a sample Cobertura report, there's a sample report here.
If you've never heard some of the benefits of code coverage in general, I wrote a short blog about the benefits of code coverage here.
I'll just provide the entire ant build script here, and then discuss some of the features afterwards:
<project name="WikiTeX Editor" basedir=".." default="package"> <!-- project-specific variables --> <property name="package.name" value="wikitex.jar" /> <property name="properties.file" value="wikitex.properties" /> <property name="manifest.main.class" value="com.devdaily.wikitex.Main" /> <property name="manifest.built.by" value="Alvin J. Alexander" /> <property name="manifest.created.by" value="DevDaily.com" /> <property name="version.number" value="1.0" /> <property name="build.number" value="1984" /> <property name="lib.dir" value="lib" /> <property name="lib.cobertura.dir" value="${lib.dir}/cobertura-1.9" /> <property name="cobertura.jar.file" value="${lib.dir}/cobertura-1.9/cobertura.jar" /> <property name="src.tests.dir" value="src-tests" /> <property name="cob.ser.file" value="cobertura.ser" /> <property environment="env" /> <property name="build.dir" value="build" /> <property file="${build.dir}/build.${env.HOSTNAME}" /> <property name="src.dir" value="src" /> <property name="reports.dir" value="reports" /> <property name="resources.dir" value="resources" /> <property name="dest.dir" value="target" /> <property name="dest.dir.classes" value="${dest.dir}/classes" /> <property name="dest.dir.lib" value="${dest.dir.classes}/lib" /> <property name="package.file" value="${dest.dir}/${package.name}" /> <!-- cobertura properties --> <property name="cobertura.dir" value="cobertura" /> <property name="cob.instrumented.dir" value="${cobertura.dir}/instrumented" /> <property name="cob.reports.dir" value="${cobertura.dir}/reports" /> <property name="cob.reports.xml.dir" value="${cob.reports.dir}/junit-xml" /> <property name="cob.reports.html.dir" value="${cob.reports.dir}/junit-html" /> <property name="cob.coverage.xml.dir" value="${cob.reports.dir}/cobertura-xml" /> <property name="cob.coverage.html.dir" value="${cob.reports.dir}/cobertura-html" /> <path id="build.class.path"> <fileset dir="lib"> <include name="**/*.jar" /> </fileset> </path> <target name="clean"> <delete dir="${dest.dir}" /> <delete dir="${reports.dir}" /> <delete dir="${cobertura.dir}" /> <delete file="${cob.ser.file}" /> </target> <target name="prepare" depends="clean"> <mkdir dir="${dest.dir}" /> <mkdir dir="${dest.dir.classes}" /> <mkdir dir="${dest.dir.classes}/META-INF" /> <mkdir dir="${reports.dir}" /> <!-- cobertura directories --> <mkdir dir="${cobertura.dir}" /> <mkdir dir="${cob.instrumented.dir}" /> <mkdir dir="${cob.reports.xml.dir}" /> <mkdir dir="${cob.reports.html.dir}" /> <mkdir dir="${cob.coverage.xml.dir}" /> <mkdir dir="${cob.coverage.html.dir}" /> </target> <target name="compile" depends="prepare"> <echo>=== COMPILE ===</echo> <echo>Compiling ${src.dir} files ...</echo> <javac debug="on" srcdir="${src.dir}" destdir="${dest.dir.classes}" includes="com/**"> <classpath refid="build.class.path" /> <!-- classpath refid="cobertura.classpath" --> </javac> <!-- compile files on the src-tests path --> <echo>Compiling ${src.tests.dir} files ...</echo> <javac debug="on" srcdir="${src.tests.dir}" destdir="${dest.dir.classes}" includes="com/**"> <classpath refid="build.class.path" /> <!-- classpath refid="cobertura.classpath" --> </javac> </target> <target name="package" depends="compile"> <echo>=== PACKAGE ===</echo> <!-- convert classpath to flat list for use in manifest task --> <pathconvert property="mf.classpath" pathsep=" "> <path refid="build.class.path" /> <flattenmapper /> </pathconvert> <tstamp/><!-- needed for TODAY --> <manifest file="MANIFEST.MF"> <attribute name="Built-By" value="${manifest.built.by}"/> <attribute name="Created-By" value="${manifest.created.by}"/> <attribute name="Main-Class" value="${manifest.main.class}"/> <attribute name="Implementation-Version" value="${version.number}-b${build.number}"/> <attribute name="Built-Date" value="${TODAY}"/> <attribute name="Class-Path" value="${mf.classpath}" /> </manifest> <jar basedir="${dest.dir.classes}" destfile="${package.file}" includes="**/*.*" excludes="**/*Test*" manifest="MANIFEST.MF" /> <copy todir="${dest.dir}"> <fileset dir="${lib.dir}"> <exclude name="${lib.cobertura.dir}" /> <exclude name="junit*" /> <include name="*.jar"/> <include name="*.zip"/> </fileset> </copy> <!-- move this file before the 'jar' task (and put it in the 'classes' dir) if you want to include it in the jar --> <copy file="${resources.dir}/log4j.properties" tofile="${dest.dir}/log4j.properties" overwrite="true" /> <copy file="${resources.dir}/${properties.file}" tofile="${dest.dir}/${properties.file}" overwrite="true" /> <delete dir="${dest.dir.classes}" /> </target> <!-- INSTALL (no install target is needed) --> <!-- classpath for test needs to include the dist/classes directory --> <path id="classpath.test"> <pathelement location="${dest.dir.classes}" /> <fileset dir="lib"> <include name="**/*.jar" /> </fileset> </path> <!-- may want to modify this, and only allow deployment if the tests run --> <target name="test" description="Run JUnit Tests" depends="compile"> <echo>=== UNIT TESTS ===</echo> <delete> <fileset dir="${reports.dir}" includes="**/TEST-*.txt"/> </delete> <junit dir="./" printsummary="yes" fork="yes" haltonfailure="yes"> <formatter type="plain"/> <batchtest fork="yes" todir="${reports.dir}"> <fileset dir="${dest.dir.classes}"> <include name="**/Test*"/> <include name="**/*Tests*"/> <exclude name="**/AllTests*"/> <exclude name="**/TestingUtils*"/> </fileset> </batchtest> <classpath> <path refid="classpath.test"/> </classpath> </junit> </target> <!-- cobertura task definition --> <path id="cobertura.classpath"> <fileset dir="${lib.dir}"> <include name="${cobertura.jar.file}" /> <include name="**/*.jar" /> </fileset> </path> <taskdef classpathref="cobertura.classpath" resource="tasks.properties"/> <target name="instrument" depends="compile"> <cobertura-instrument todir="${cob.instrumented.dir}"> <fileset dir="${dest.dir.classes}"> <include name="**/*.class"/> </fileset> </cobertura-instrument> </target> <target name="cover-test" depends="instrument"> <junit dir="./" failureproperty="test.failure" printsummary="yes" fork="true" haltonerror="true"> <classpath location="${cobertura.jar.file}"/> <classpath location="${cob.instrumented.dir}"/> <classpath> <path refid="build.class.path"/> </classpath> <batchtest fork="yes" todir="${reports.dir}"> <fileset dir="${cob.instrumented.dir}"> <include name="**/Test*"/> <include name="**/*Tests*"/> <include name="org/jaxen/javabean/*Test*" /> <exclude name="**/TestingUtils*"/> <exclude name="**/AllTests*"/> </fileset> </batchtest> </junit> </target> <!-- run this target to generate the coverage reports --> <target name="coverage-report" depends="cover-test"> <cobertura-report srcdir="${src.dir}" destdir="${cobertura.dir}"/> </target> </project>
I could write about this build script for a long time, but I don't have that much time today, so here are the highlights, especially the Cobertura-related highlights:
- Any Ant build script like this is dependent on your directory structure. If you're used to Ant you can infer my directory names from the properties I set early in the script, but to summarize them here, my project directories are build, classes, lib, src, src-test, and reports. Also, this build.xml file is kept in the build subdirectory, and I run Ant from that directory.
- The tasks I typically run are
test
orcompile
(depending on where I am in the development process), orcoverage-report
when I want to see my code coverage results. - The
package
task creates an executable jar file that can be easily run by users with thejava-jar
command. - The executable jar file includes a manifest file, and that manifest file includes a classpath that I create dynamically, based on the jar files in my
lib
directory.