5.3 Preparing for Test-First Development

5.3.1 Problem

You want to configure your development environment to support test-first development with HttpUnit, JUnit, Tomcat, and Ant.

5.3.2 Solution

Create an Ant buildfile to automatically build and deploy your web application. The buildfile allows you to quickly redeploy and test after each change to your code.

5.3.3 Discussion

The example shown in this recipe relies on Tomcat 4.0 or later, as well as Ant Version 1.5. It uses Tomcat's manager application to deploy and redeploy the web application while Tomcat is running. The ability to redeploy a modified web application while the server is running is critical for test-first development because it takes too long to restart most servers. A successful XP approach depends on your ability to make lots of small code changes quickly.

In order to test using Ant's junit task, you should copy servlet.jar, httpunit.jar, junit.jar, and Tidy.jar to Ant's lib directory. This makes it easy to ensure that all of the required JAR files are loaded using the same Java ClassLoader when you are running your tests. Ant class loading issues were discussed in Recipe 3.15.

The first part of your buildfile should define a classpath:

<path id="classpath.project">
  <pathelement path="${dir.build}"/>
</path>

This path picks up all class files from your build directory. Ant also includes all of the JAR files from the ANT_HOME/lib directory. This allows you to compile the code with this target:

<target name="compile" depends="prepare"
        description="Compile all source code.">
  <javac srcdir="${dir.src}" destdir="${dir.build}">
    <classpath refid="classpath.project"/>
  </javac>
</target>

And next, your buildfile should have a target to generate the WAR file:

<target name="war" depends="compile">
  <!-- build the newsletter example from the HttpUnit chapter -->
  <war warfile="${dir.build}/news.war"
       webxml="httpunit_chapter/web.xml">
    <fileset dir="httpunit_chapter">
      <exclude name="web.xml"/>
    </fileset>
    <classes dir="${dir.build}">
      <include name="com/oreilly/javaxp/httpunit/**/*.class"/>
    </classes>
  </war>
</target>

This target assumes that your development environment has a directory named httpunit_chapter containing any HTML files comprising your web application. The deployment descriptor, web.xml, is also found in this directory. Finally, the <classes> element specifies where to find the .class files for the web application. Although your environment will likely be quite different, you will want targets similar to this in order to quickly generate your own WAR file.

Once you have figured out how to generate your WAR file, turn your attention to deployment. Tomcat's manager application uses an HTTP interface for its functionality. To deploy, use the following command:

http://localhost:8080/manager/install?path=/news&war=jar:file:/path/to/news.war!/

If you need to redeploy, you must first undeploy the application. Here is the command to undeploy the application:

http://localhost:8080/manager/remove?path=/news

The manager application fails unless you first configure a username and password, so you must edit the tomcat-users.xml file in Tomcat's conf directory. Simply add a user with the manager role:

<tomcat-users>
  <!-- add this user to access the manager application -->
  <user name="eric"   password="secret" roles="manager" />
  <user name="tomcat" password="tomcat" roles="tomcat" />
  <user name="role1"  password="tomcat" roles="role1"  />
  <user name="both"   password="tomcat" roles="tomcat,role1" />
</tomcat-users>

We are almost finished! Now that the user is configured and you know how to run the manager application, you can write some Ant targets to deploy and undeploy the application.

This example requires Ant Version 1.5 or later because prior to Ant 1.5, there was no standard way to pass the required username and password to a web server from an Ant task. Ant 1.5 adds HTTP BASIC authentication support to its get task, thus supporting the username and password.

The following code example shows how to use Ant's get task to undeploy and deploy a web application to Tomcat.

<target name="undeploy">
  <!-- use the manager app to undeploy -->
  <get src="${url.manager}/remove?path=/news"
       dest="${dir.build}/undeployOutput.txt"
       username="eric"
       password="secret"
       verbose="true"/>

  <!-- the manager app does not delete the directory for us -->
  <delete dir="${env.TOMCAT_HOME}/webapps/news"/>

  <!-- echo the results to the console -->
  <!-- NOTE: This reports an error when you first run it, 
       because the app is not initially deployed -->
  <concat>
    <filelist dir="${dir.build}" files="undeployOutput.txt" />
  </concat>
</target>


<target name="deploy" depends="war,undeploy">
  <!-- 
    Convert the project-relative path, such as "build/news.war",
    into a fully-qualitifed path like "C:/dev/news/build/news.war" 
    -->
  <pathconvert dirsep="/" property="fullWarDir">
    <path>
      <pathelement location="${dir.build}/news.war"/>
    </path>
  </pathconvert>

  <!-- Use the manager app to deploy -->
  <get src="${url.manager}/install
            ?path=/news&amp;war=jar:file:${fullWarDir}!/"
       dest="${dir.build}/deployOutput.txt"
       username="eric"
       password="secret"
       verbose="true"/>
  <!-- echo the results to the console -->
  <concat>
    <filelist dir="${dir.build}" files="deployOutput.txt" />
  </concat>
</target>

For the icing on the cake, you should define a target that executes your unit tests.

  <target name="junit" depends="deploy">
    <junit printsummary="on" fork="false" haltonfailure="false">

      <classpath refid="classpath.project"/>

      <formatter type="plain"/>

      <batchtest fork="false" todir="${dir.build}">
        <fileset dir="${dir.src}">
          <include name="**/Test*.java"/>
          <exclude name="**/AllTests.java"/>
        </fileset>
      </batchtest>
    </junit>
  </target>

Notice the dependencies. When you type ant junit, the deploy target is executed. This, in turn, causes the WAR file to be built, which causes the code to be compiled.

Setting up an efficient development environment is well worth the effort. After you write each new unit test and small piece of functionality, you can type ant junit to compile, build the WAR file, deploy, and run all tests.

5.3.4 See Also

Recipe 3.15 discusses Ant class loading issues. Chapter 10 contains a much more sophisticated buildfile that handles all sorts of deployment and server startup issues.