JUnit Tutorial

Introduction

This is not a thorough treatise on JUnit. But if you haven't used JUnit before, I suspect that it is about as much as you need.

Getting JUnit

You can get the complete JUnit distribution by going to JUnit's Sourceforge page for downloads. The download button is towards the middle of the Web page, on the righthand side. At the time of writing the current version is 3.8.1, from September 2002.

However, the current JUnit jar comes bundled in with the JXCL distribution and in our experience that's all that you need.

Installation and the Classpath

The jarfile needs to be installed where it will be on whatever classpath you are using. JXCL does this for you; all necessary jars are organized in subdirectories under jxcl/lib and classpaths are set up to automatically point to the right place. You can do the same with your project either by copying our configuration and build files and by using Xlattice's ProjMgr to automate the process.

It's important to keep in mind that there are at least three relevant classpath's -- the one seen by Java when it starts running Ant; the one seen by Ant; and the classpath passed by Ant to JUnit and UCovered when they are running tests.

Ant must be on the first classpath. The second classpath is defined in build.xml. JUnit and UCovered must be on that classpath, or Ant will not be able to find them to run them. The third classpath is defined in the <ucovered> element in build.xml. That must include junit.jar, and jxcl.jar. It's easiest to manage these requirements if all four jars are in the same place and on all three classpaths.

Setting Up Your Work Directories

While it is by no means necessary, it makes things much easier if unit tests are set up in a parallel hierarchy to that used for the software under test. The JXCL software is set up like this:

  src
    java
      org
        jxcl
          cl
            JXClassLoader.java
  src
    test
      org
        jxcl
          cl
            TestJXClassLoader.java

The target directory, which the compiler writes compiled classes to, is organized in exactly the same way.

It's convenient to work with three windows open, one showing a unit test directory, one the corresponding source code directory, and one the base directory for the component. This makes it particularly easy to do test-driven development: you write your unit tests in one window, run them in another, and make source code changes in the third. This can be done in a very fast iterative process.

Writing New Code

If you are writing new code from scratch, the approach sketched out so far can make for very rapid software development. Each class in the java side of the source hierarchy is matched with a class on the test side. The name of the second is that of the first prefixed with "Test". Code development goes in a fast loop:

  • write Javadoc comments specifying the code
  • write some code
  • write tests verifying that the code is correct
  • run Ant
  • regenerate Javadocs periodically
  • when all tests succeed, run UCovered coverage tests to verify that testing is thorough
This cycle should be iterated several times a day, so that code always either works or is very close to working.

JUnit Tests

Most JUnit tests are written like this:

  • Create a skeleton in the right package.
  • import junit.framework.*
  • import whatever other packages you need
  • create a public class extending TestCase
  • add a constructor with a single String argument that passes the argument to super
  • optionally add a setUp
  • optionally add a tearDown
  • add a lot of public void testX(){}

This gives you:

package com.xyz.stuff;

import junit.framework.*;

public class TestStuff extends TestCase {
    public TestStuff(String name) {
        super(name);
    }
    protected void setUp() {
    }
    protected void tearDown() {
    }
    // there are normally many methods like this
    public void testX() {
    }
    public void testY() {
    }
    public void testZ() {
    }
    // and more
}

While there are other JUnit classes, TestCase is the workhorse. If you write tests to this pattern, Ant will grope around, find all public void methods whose names begin with test, and run them. That's all there is to it.

setUp

JUnit runs this method before running each of the tests.

Frequently you need to create a standard fixture to run your code against. This might be something fairly complex, perhaps an elaborate linked list or some other data structure used for exercising your code. Generally you will want a fresh copy of this for each test. You put the code for generating this fixture in setUp.

tearDown

This is the companion to setUp, run after each test to take down the test fixture.

In our experience tearDown is used much less commonly than setUp. It does no harm to leave a stub in your TestCase.

Assertions

Assertions are what JUnit is all about. The code you are testing is doing something. Your assertions will test that after running the software under test with a certain set of inputs, you will have a specified condition. A typical assertion is something like

  // pattern:
  //   assertCondition(failureMsg, expected, actual);
  assertEquals("cube factory is failing", 8, cubeIt (2));

You can omit the message, but this is generally undesirable. To keep things rolling along, you want a clear and unambiguous description of what failed; if you don't describe the failure, when it does go wrong you will spend time trying to guess what actually failed, or you will have to go back and add the message.

There are many types of JUnit assertions, all of them documented in the Javadocs. In all cases, what is expected and the actual must be of the same type.

  • assertEquals
  • assertTrue
  • assertFalse
  • assertNull
  • assertNotNull
  • assertSame
  • assertNotSame
  • fail

All have two variants. For all except fail the first variant accepts two arguments (expected, actual) and the second three (failureMsg, expected, actual).

In actual use, the most common assertion appears to be assertEquals.

We strongly recommend that instead of reading a lot about unit testing, you write a lot of test cases. After a while, it becomes second nature, and you have understood most of what is important regarding the subject.

Then it makes a lot of sense to go back and read about it.

Make Ant Do the Work for You

As your software becomes bigger, tests take longer to run and it also becomes more difficult to track exactly what is going on. Use Ant's filesets to maintain your focus.

This is easy to do if the software under test and the tests themselves are in parallel hierarchies. This allows you to casually switch between testing individual classes, testing all of the classes in a package, and running all of the tests, with just a few keystrokes in the editor:

  <batchtest todir="${test.report.dir}">
    <fileset dir="${test.src.dir}">
      <include name="**/TestStmtCoverage.java"/>
      <include name="**/TestRegistry.java"/>
    </fileset>
  </batchtest>
  
  <!-- other sets commonly tested; the first means 'all tests'
      <include name="**/*Test*.java"/>
      <include name="**/graph/Test*.java"/>
      <include name="**/TestA2O.java"/>
      <include name="**/TestBlockCode.java"/>
      <include name="**/TestClassFactory.java"/>
   -->

The build.xml fragment above runs only two tests, TestStmtCoverage and TestRegistry. If the first line in the commented group is copied into the fileset, it runs all tests.

Fixing Old Code

Life is more difficult if you are maintaining existing code. The practical approach is usually to introduce unit testing gradually, working towards the parallel directory hierarchies described above.

This often encounters opposition from co-workers and management, partially because of the short-term expense and partially because of the intellectual and political investment in the status quo.

If you persist, you will generally find that the code which most resists this approach is the code that most needs testing, because it is the buggiest.

More Information

There are two JUnit Web sites, one their own Web site and the other at Sourceforge. Both are very good. The first has links to Web sites describing dozens of other software packages using JUnit. The second is a tutorial with many references to other articles, papers, and Web sites.


This material is copyright 2003 by James David Dixon ("Jim Dixon") and is made available under the Artistic License.