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
+++ +