Now that we have dependency management with Ivy working along with everything else covered before, we’ve covered almost everything required to start building real projects with Ant.
Another thing any real project should have, is unit tests. Thankfully, using the scaffolding already put in place in earlier parts of this series, integrating a JUnit testing task into our existing build script is really straight-forward.
The code for this part of the tutorial is available on GitHub. To see what’s new and changed, this diff is quite useful. I’ll break down what’s in that diff:
<!-- basic paths -->
...
<property name="test.dir" location="test"/>
...
To start off, adding a new property to keep track of where the test classes will live.
Your directory tree will look something like this:
Project Root
- src
- yourpackage
- YourClass.java
- test
- yourpackage
- YourClassTest.java
...
<property name="build.test.dir" location="${build.dir}/test"/>
Similarly, the output directory for compiled/built test sources.
<path id="test.classpath">
<fileset dir="${lib.dir}" includes="test/*.jar"/>
<pathelement path="${build.test.dir}"/>
</path>
Defining a classpath dedicated to tests, which includes all Ivy-downloaded libraries for testing, as well as th actual compiled test classes themselves.
It may not seem immediately useful to have a dedicated directory/path for testing libraries, however before long, you’ll likely come across various libraries and frameworks (aside form JUnit itself) which assist with and facilitate things like mocking, in-memory database testing, etc., which you’ll need to exclude from your distributable builds. It’s neater to manage these dependencies separately.
<!-- Build test classes -->
<target name="build-tests" depends="build" description="compile test source files">
<mkdir dir="${build.test.dir}"/>
<javac srcdir="${test.dir}" destdir="${build.test.dir}" includeantruntime="false" debug="on">
<classpath refid="default.classpath"/>
<classpath refid="test.classpath"/>
</javac>
</target>
This looks nearly identical to the build
target defined back in part
1,
with some changes to the variables used - this time making reference to
the test paths and properties.
It’s also added the test
classpath to the javac
task in addition to the
default.classpath
which will include the built contents of the main
src
directory.
You may also note that this target depends on the build
target - the
test classes will depend on the primary source files being built before
they can be.
<!-- Run tests -->
<target name="run-tests" depends="build-tests" description="run junit tests">
<junit printsummary="yes" fork="yes" haltonfailure="no"
errorproperty="tests.errors" failureproperty="tests.failures">
<classpath refid="default.classpath"/>
<classpath refid="test.classpath"/>
<formatter type="plain" usefile="false"/>
<batchtest>
<fileset dir="${test.dir}">
<include name="**/*Test.java"/>
</fileset>
</batchtest>
</junit>
</target>
The build target which actually executes the junit task to run our test cases.
A couple of attributes are being set here, but most importantly within
this particular script are errorproperty
and failureproperty
. Ant
will set properties with the names specified in the event of a test
error or failure. We can use these in the test
target described below
to alter the behavior of the script based on what happened within the
tests.
The formatter
element in use here is indicating we want plain-text
output, and it should only be printed to the console, rather than sent
to a file. Although I’m not using it in this particular script, a common
use-case would be to set the format type
to xml
, and use the todir
attribute of the batchtest
element to have the test reports written to
XML files, which may then be presented as fancy HTML reports, usually by
a build server such as Jenkins, which is
incredibly useful.
Finally, we are using the batchtest
tag to indicate we want to run a
batch of tests. The fileset
provided is using the test source
directory we defined, and is only going to process test classes whose
name ends in ...Test.java
. This is useful since you may want to
include helper, utility and mock classes within the test sources path,
which typically JUnit would choke on, as it looks for tests in all
provided files within the fileset
provided.
<!-- Run tests, exiting with status code 1 on error, or 2 on test failure -->
<target name="test" depends="run-tests" description="run junit tests, and fail the build on error or failure">
<fail if="tests.errors" message="Error encountered while executing tests" status="1"/>
<fail if="tests.failures" message="Tests failed" status="2"/>
</target>
The final change to build.xml
, the actual test
target itself.
Used in combination with the attributes passed to the junit
task
defined in the run-tests
, this target allows us to fail the build with
varying exit codes to allow automated build tools to react or report
differently, based on the outcome of the tests, without needing to parse
logs or the test reports. Mostly this is a convenience feature.
That’s it for the build.xml
changes, now a minor adjustment to
ivy.xml
to download JUnit for us:
...
<conf name="test" extends="default"/>
...
<dependencies>
...
<!-- test dependencies -->
<dependency org="junit" name="junit" rev="4.11" conf="test->default"/>
...
Pretty straight-forward, with the addition of the test
configuration.
Ivy
Configurations
offer a way of grouping different sets of dependencies for different
purposes. In our case, the test
configuration can be used for all
test-related third-party dependencies, including JUnit itself! For
example if you were developing a database application, and wanted your
tests to run against an in-memory Derby database rather than something
external, you’d configure that dependency as part of the test
configuration, which would mean it would not be included in your
default
libraries used by your (probably) redistributable code.
That’s about all there is to it. You can now execute the following to run unit tests in your project:
$ ant test
Other parts in this series: