AntUnit
++ + Idea +
+Initially all tests for Ant tasks were written as individual + JUnit test cases. Pretty + soon it was clear that most tests needed to perform common tasks + like reading a build file, intializing a project instance with + it and executing a target. At this point BuildFileTest + was invented, a base class for almost all task test cases.
+BuildFileTest works fine and in fact has been picked up by the Ant-Contrib Project + and others as well.
+Over time a new pattern evolved, more and more tests only
+ executed a target and didn't check any effects. Instead that
+ target contained the assertions as a <fail>
+ task. This is an example taken from the build file for the
+ ANTLR task (using Ant 1.7 features):
+ <target name="test3" depends="setup">
+ <antlr target="antlr.g" outputdirectory="${tmp.dir}"/>
+ <fail>
+ <condition>
+ <!-- to prove each of these files exists;
+ ANTLR >= 2.7.6 leaves behind new (.smap) files as well. -->
+ <resourcecount when="ne" count="5">
+ <fileset dir="${tmp.dir}">
+ <include name="CalcParserTokenTypes.txt" />
+ <include name="CalcParserTokenTypes.java" />
+ <include name="CalcLexer.java" />
+ <include name="CalcParser.java" />
+ <include name="CalcTreeWalker.java" />
+ </fileset>
+ </resourcecount>
+ </condition>
+ </fail>
+ </target>
+
+ where the corresponding JUnit testcase has been reduced + to
+
+...
+public class ANTLRTest extends BuildFileTest {
+
+ private final static String TASKDEFS_DIR = "src/etc/testcases/taskdefs/optional/antlr/";
+
+ public ANTLRTest(String name) {
+ super(name);
+ }
+
+ public void setUp() {
+ configureProject(TASKDEFS_DIR + "antlr.xml");
+ }
+
+ public void tearDown() {
+ executeTarget("cleanup");
+ }
+
+ public void test3() {
+ executeTarget("test3");
+ }
+...
+}
+
+ This approach has a couple of advantages, one of them is that + it is very easy to translate an example build file from a bug + report into a test case. If you ask a user for a testcase for a + given bug in Ant, he now doesn't need to understand JUnit or how + to fit a test into Ant's existing tests any more.
+AntUnit takes this approach to testing even further, it
+ removes JUnit completely and it comes with a set of predefined
+ <assert> tasks in order to reuse common kind
+ of checks.
It turns out that AntUnit lends itself as a solution to other + problems as well. The assertions are an easy way to validate a + setup before even starting the build process, for example. + AntUnit could also be used for functional and integration tests + outside of the scope of Ant tasks (assert contents of databases + after running an application, assert contents of HTTP responses + ...). This is an area that will need more research.
++ + Concepts +
++ + antunit Task +
+The <antunit> task drives the tests much like + <junit> does for JUnit tests.
+When called on a build file, the task will start a new Ant + project for that build file and scan for targets with names + that start with "test". For each such target it then will
+-
+
- Execute the target named setUp, if there is one. +
- Execute the target itself - if this target depends on + other targets the normal Ant rules apply and the dependent + targets are executed first. +
- Execute the target names tearDown, if there is one. +
+ + Assertions +
+The base task is <assertTrue>. It
+ accepts a single nested condition and throws a subclass of
+ BuildException named AssertionFailedException if that
+ condition evaluates to false.
This task could have been implemented using
+ <macrodef> and <fail>,
+ but in fact it is a "real" task so that it is possible to
+ throw a subclass of BuildException. The
+ <antunit> task catches this exception and
+ marks the target as failed, any other type of Exception
+ (including other BuildException) are test errors.
Together with <assertTrue> there are
+ many predefined assertions for common conditions, most of
+ these are only macros.
+ + Other Tasks +
+The <logcapturer> captures all messages
+ that pass Ant's logging system and provides them via a
+ reference inside of the project. If you want to assert
+ certain log messages, you need to start this task (prior to
+ your target under test) and use the
+ <assertLogContains> assertion.
<expectFailure> is a task container that
+ catches any BuildException thrown by tasks nested into it. If
+ no exception has been thrown it will cause a test failure (by
+ throwing an AssertionFailedException).
+ + AntUnitListener +
+Part of the library is the AntUnitListener
+ interface that can be used to record test results. The
+ <antunit> task accepts arbitrary many listeners and
+ relays test results to them.
Currently only a single implementation
+ <plainlistener> modelled after the "plain"
+ JUnit listener is bundeled with the library.
+ + Examples +
+This is a way to test that <touch>
+ actually creates a file if it doesn't exist:
+<project xmlns:au="antlib:org.apache.ant.antunit">
+ <!-- is called prior to the test -->
+ <target name="setUp">
+ <property name="foo" value="foo"/>
+ </target>
+
+ <!-- is called after the test, if if that causes an error -->
+ <target name="tearDown">
+ <delete file="${foo}" quiet="true"/>
+ </target>
+
+ <!-- the actual test case -->
+ <target name="testTouchCreatesFile">
+ <au:assertFileDoesntExist name="${foo}"/>
+ <touch file="${foo}"/>
+ <au:assertFileExists name="${foo}"/>
+ </target>
+</project>
+
+ When running a task like
++ <au:antunit> + <fileset dir="." includes="touch.xml"/> + <au:plainlistener/> + </au:antunit> ++
from a buildfile of its own you'll get a result that looks like
+++ +


