Marty Andrews

artful code

Friday, July 16, 2004

CI Demo Part 5 - Something to play with

I've uploaded all of the artefacts for the continuous integration demo I've been working on, and put the details for how to run it on the articles section of the site. It includes examples of the things I've been blogging about recently so you can see them used in anger.

Please have a go if you're interested and have some time to spare. I'd love to get some feedback. Oh - and thanks to Mike Williams for all your help.

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).

Monday, July 12, 2004

Decorating Test Suites with Jetty

On previous web-based projects that I've worked on, the build script assumed that a web-container was running that it could deploy the application to for functional tests. In the continuous integration demo that I was working on recently, Mike showed me a cool technique that he had found to eliminate the dependancy.

He's beaten me to it though, and already written about how to decorate test suites with Jetty.

Thursday, July 8, 2004

Maintainability in Ant build scripts

I've worked on dozens of projects that used Ant build scripts, with team sizes ranging from just me to tens of developers. Big projects tend to have big Ant scripts, and they can be a maintenance nightmare. I've developed a structural style for scripts that I find easier to maintain and explain on larger projects.

Take the following Ant script as a typical example.

<project name="foo" basedir="." default="test"><target name="init" description="Prepare directories for build">...</target><target name="compile" depends="init" description="Compiles code">...</target><target name="test" depends="compile" description="Tests code">...</target></project>

The script has only three targets in it. All targets have a description, and two of them have a dependancy on another target. Those descriptions and dependancies are scattered throughout the file. It is possible to discover the descriptions of targets publicly available by running ant -projecthelp, and the dependancies will be seen when the script runs, but neither are easily viewable at a glance. This is especially true when your Ant script has grown to hundreds of lines and tens of targets.

Here's the same script modified to follow my different convention

<project name="foo" basedir="." default="test"><target name="-init">...</target><target name="-compile">...</target><target name="-test">...</target><!-- Private targets used only for dependancies --><target name="--init"    depends="-init"/><target name="--compile" depends="-init,-compile"/><target name="--test"    depends="--compile,-test"><!-- Public targets with description --><target name="init"    depends="--init"    description="Prepare directories for build"><target name="compile" depends="--compile" description="Compiles code"><target name="test"    depends="--test"    description="Tests code"></project>

Most of the targets in this script are now "private" targets. Basically - anything that starts with a hyphen can't be run on the command line by Ant, as it thinks you are passing a parameter to the Ant script rather than specifying a target. None of these private targets have any dependancies or descriptions.

All dependancies between targets can be seen at a glance in one section. They are again all listed on private targets, but these ones do no work other than describing dependancies.

All descriptions can be seen at a glance in one section. They are listed on public targets, and delegate to a single other private target which handles the dependancies for it.

Thats all there is to it really. The purist in me doesn't like it, because the test target really does depend on the compile target, so thats where it should be listed. My pragmatic side almost always wins though, and I've seen enough confusion in Ant scripts to adopt this pattern for all my new builds.

CI Demo Part 4 - A new start

So I've finally found a bit of spare time to do some work on that continuous integration demo again. Mike Williams has been helping me out a bit, and we've made some good progress.

The app is pretty simple - it's a To-Do list. We built it using a slightly different set of technologies than I'd originally anticipated. They were:

  • Subversion
  • Java
  • Ant
  • JUnit
  • JCoverage
  • Simian
  • Jetty
  • JWebUnit
  • Cruise Control
  • WebWork
  • Velocity

There are a few noticeable differences from my original list here. Subversion was used for simplicity of setup on Windows. I anticipate that most people will set this up on Wintel boxes, and CVS caused a bit of frustration. Webwork I started trying it just to see what it was like, and became a fan so kept using it. Velocity is in the same boat.

No FIT (or FIT-like) tests are included yet. Thats on my To-Do to-do list (pun intended). I'm also not uploading it all yet, as I need to clean a couple of bits up. I will however describe some of the key features in other blog entries.