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.
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.
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.
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.
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:
Most JUnit tests are written like this:
super
setUp
tearDown
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.
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.
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 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.
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.
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.
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.
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.