Marty Andrews

artful code

Tuesday, July 13, 2004

A faster alternative to batchtest with JUnit in Ant

Want faster Ant build times? One of the most common culprits I've seen for slow builds is the use ofbatchtest under the junit task while it has fork="yes" set.

People end up in this state for a variety of reasons. The batchtest example inthe Ant docs has fork="yes" set. So does the example in the cruisecontrol docs. Perhaps they started not forking a VM, but got clashes with XML (or other)libraries, and that was the easiest fix. Whatever the reason, I've seen lots of people do it,and there is a better way.

Lets start with a typical example. Here's what your Ant test target might look like to start offwith:

<target name="test"><junit fork="yes"><classpath refid="test.classpath"/><formatter type="plain" usefile="false"/><batchtest><fileset dir="${test.classes.dir}"><include name="**/*Test.class"/><exclude name="**/acceptance/**/*Test.class"/></fileset></batchtest></junit></target>

The basic approach is to convert this Ant script which runs lots of tests into an Ant scriptthat runs only one test. Several things need to happen though. Firstly, the virtual machinestill needs to be forked, to make sure classpath issues are not re-introduced. Secondly, thetest has to become a suite so that all of the tests will still be run. Finally, some sort offilter mechanism needs to be put into the suite that matches the fileset filtering mechanismin the batchtest

The Ant script now becomes a bit simpler. The batchtest section is gone, anda single test name is in its place: com.thoughtworks.todolist.AllTests.

<target name="test"><junit fork="yes"><classpath refid="test.classpath"/><formatter type="plain" usefile="false"/><test name="com.thoughtworks.todolist.AllTests"/></junit></target>

To get the test suite to work as per batchtest, I use a utility from theJUnit Addons project. Itdynamically builds up a test suite from a directory structure with custom filters toinclude or exclude classes. My test suite looks like this:

package com.thoughtworks.todolist;import junit.framework.Test;import junitx.util.DirectorySuiteBuilder;import junitx.util.SimpleTestFilter;public class AllTests {public static Test suite() throws Exception {DirectorySuiteBuilder builder = new DirectorySuiteBuilder();builder.setFilter(new SimpleTestFilter() {public boolean include(Class clazz) {return super.include(clazz) &&!getPackageName(clazz).equals("com.thoughtworks.todolist.acceptance");}});return builder.suite("build/test/classes");}}

The use of the DirectorySuiteBuilder class replaces the batchtestcomponent of the Ant build script, and the use of the SimpleTestFilter replacesthe need for the nested fileset element.

That's all there is to it. In my simple application that so far has a grand total of 11 unittests, I managed to cut my build time from 4 seconds down to 2 seconds. Phew! Pretty quick huh?Well - maybe in my case it didn't matter so much, but I've seen this cut minutes of builds for"real" projects before. Let me know how it goes for you.

Oh yeah - as an added bonus, you now have a stand-alone JUnit test suite that will always beup to date with all of your unit tests. It has no dependancy on Ant, so you can run it by itselfinside your IDE (or whatever environment you use for JUnit tests).

No comments:

Post a Comment