To illustrate test-driven development on a range of scenarios, we are going to build some simple moose-monitoring software.
Moose, known as elk in Europe, are the largest members of the deer family. An estimated 2 million moose exist in the world. To better count them, the (fictional) World Organization for Moose, a.k.a. WOM, has hired us to develop moose-monitoring software. WOM wants rangers to be able to track and record the moose they see.
This article assumes you're comfortable working with Java and Ant, Maven or the CLASSPATH, and that you have written at least one or two simple JUnit tests.

A moose. Just like the ones we'll be counting.
We begin our project by making a Moose class:
package moose;
import java.util.Date;
public class Moose {
private Date dateTimeObserved;
private int age;
public Moose(Date dateTimeObservedParam, int estimatedAge) {
this.dateTimeObserved = dateTimeObservedParam;
this.age = estimatedAge;
}
}
Although moose can live up to 25 years, most moose taken by hunters are much younger, often only 2 or 3 years old. For WOM
reports, we want to know if a moose is more than a certain age, so we add method isOlderThan(int).
Its unit test looks like this:
public void testIsOlderThan() {
Moose moose = new Moose(new Date(), MOOSE_AGE);
assertTrue("Moose age "+ MOOSE_AGE +" should of been older than "+ TEST_AGE, moose.isOlderThan(TEST_AGE) );
}
And the method is:
boolean isOlderThan(int contenderAge) {
return this.age >= contenderAge;
}
The best time for sighting moose is at dawn or dusk, as it is for most animals. WOM wants us to track which ranger recorded
the moose sighting and then be able to query Moose via a String getObserverName() method.
Unfortunately the ranger entry comes in the form of a third-party PersonnelUnit class, which is a monster (although not as big as the Alaskan moose, the biggest of all moose) that we can't construct without
an LDAP (lightweight directory access protocol) server.
To test getObserverName(), we could start an LDAP server, seed it with data, run our test, then tear the whole thing down. That would be integration
testing, not unit testing, and, well, we'd rather write code. Instead, we create a Ranger interface with just the method we need:
public interface Ranger {
String getName();
}
This gives us our first unit-testing heuristic: Isolate external dependencies behind interfaces.
We need to change the Moose constructor to also take a Ranger. It becomes:
... above as before...
private Ranger observer;
public Moose( Date dateTimeObservedParam,
int estimatedAge,
Ranger observedBy)
{
this.dateTimeObserved = dateTimeObservedParam;
this.age = estimatedAge;
this.observer = observedBy;
}
... below as before ...
Later, we'll make an implementation of Ranger that delegates the PersonnelUnit for inclusion in the final product. For now, all we need is a simple implementation that gives back the name we want—remember
we are testing Moose not Ranger.
This type of simple implementation, where we hard code values and/or define method-call expectations, is called a mock object. Mock objects are needed so often during testing that you can select from a range of libraries to create them for you. One of the best is jMock. It uses the dynamic proxies introduced in J2SE 1.3 to let us create interface implementations at runtime.
To use jMock, we must first change our test to extend MockObjectTestCase, and call the superclass in setup() and teardown():
public class TestMoose extends MockObjectTestCase {
public void setUp() throws Exception {
super.setUp();
}
public void tearDown() throws Exception {
super.tearDown();
}
... the rest as before ...
Using jMock, the test for getObserverName() is simple:
public void testObserverName() {
Mock rangerMock = mock(Ranger.class);
rangerMock.expects( once() ).method("getName").will( returnValue(RANGER_NAME) );
Moose moose = new Moose(new Date(), MOOSE_AGE, (Ranger) rangerMock.proxy() );
assertEquals("Moose did not report correct ranger", RANGER_NAME, moose.getObserverName() );
}
Let's run through this line by line:
Mock rangerMock = mock(Ranger.class);
This line creates a Mock that pretends to implement the Ranger interface. Calling rangerMock.proxy() returns a Ranger.
rangerMock.expects( once() ).method("getName").will( returnValue(RANGER_NAME) );
Here is our juicy line. Its functionality should be obvious: It tells Mock to expect the method getName() to be called exactly once, and, when it is called, it should return the value of RANGER_NAME. At the end of the test, when our tearDown() method calls super.tearDown(), our superclass MockObjectTestCase will check that all expectations were satisfied, and fail the test if they weren't.
Moose moose = new Moose(new Date(), MOOSE_AGE, (Ranger) rangerMock.proxy() );
This line creates our moose. Note how we get an implementation of Ranger off the Mock by calling its proxy() method.
assertEquals("Moose did not report correct ranger", RANGER_NAME, moose.getObserverName() );
Finally, here is the test itself. Easy. Well, it is here, because the Ranger interface is conveniently handed to us, which isn't always the case.
Moose like to feed on pondweed. Moose themselves are food for wolves and grizzly bears, and moose calves are preyed upon by black bears. Moose are also vulnerable to a disease carried by white-tailed deer (don't worry, moose are neither a threatened nor endangered species). Because of moose's interactions with other species, WOM has asked us to send a message to other teams whenever a moose is cited. These messages are picked up by rangers studying bears, wolves, deer, and pondweed (moose eat a lot of pondweed, about 25 kg a day in the summer).
The messaging system in our project is an enterprise service bus, such as Tibco Rendezvous, IBM MQSeries, or a JMS (Java Message
Service) implementation. Like the PersonnelUnit class that needed an LDAP server, this messaging system requirement could make testing difficult. How can one of these objects
exist without a full service bus behind it? How are we going to get a hold of this messaging system? The initial temptation
is to pass the buck to the integration testing team (Yeah, right, who has one of those?), but really all we need is an interface
and a pattern.
Archived Discussions (Read only)