- test your class functionalities
- test it properly in isolation
- convey the specification of the class as clearly as possible
Setting-up of an input object hierarchy
Our tested class is a factory that takes an object hierarchy build from a parser and creates another hierarchy of objects.
To make an analogy with an xml configuration file, the first hierarchy is a set of xml nodes and attributes, the second one is a configuration object with specific composed options. The issue is: how do you build the input object hierarchy in a way that's readable and really conveys its expected structure?
The unreadable way would go like that (in java):
AtomParameter attribute1 = new AtomParameter("attribute1");
AtomParameter attribute2 = new AtomParameter("attribute2");
ListParameter classList = new ListParameter();
AtomParameter class = new AtomParameter("class");
ListParameter modelList = new ListParameter();
And the readable way goes like:
Parameter parameter = list(atom("class"), list(atom("attribute1), atom("attribute2"));
By implementing the proper 'list' and 'atom' functions (returning List/Atom Parameter) the resulting code conveys the expected structure much more clearly. Unit tests as a software specification gets more real!
Testing in isolation
Not a new subject here, we use jMock to isolate our class. However, here's a pattern we've used some times. Our factory class declares 2 constructor methods:
public static ModelFactory createFactory(Project project);
public static ModelFactory createFactory(Project project, Translator translator);
In that case, the first method uses a default Translator implementation and the second one allows for another Translator, which is going to be our mock object of course. The second method may look like a pure test-only method but if you come about it, it is just extending the Factory API towards more flexibility.
By the way, I would also like to point out in this entry, one of the difficulties with mock objects in general. There is no way to specify the protocol for using an interface in a central place. If operation1() must always be called before operation2() on an interface, the mock expectations won't really declare that. Moreover, the expected protocol cannot be enforced from any unit test where the interface is called.
Protocol errors will then be catched by integration tests (acceptance tests for instance). The creator of jMock, Nat Pryce, has attempted to get around this, but he ultimately thinks this is too complex to do in Java (see the pragprog mailing list archive).
JUnit has blessed us with quite a few assertXXX methods but I have recently followed the advice found in this article, and it really changed the way we use assertions.
For instance, testing our factory goes like this:
assertThat(aFactoryUsing(model1).createInstance(parameters), containsSlots("a", "b"));
assertThat is an assertion method from MockObjectTestCase
aFactoryUsing() is a method creating a properly setup class to test
createInstance() is the tested method
containSlots() is a method returning a custom Constraint object (from the jMock framework) and doing the necessary checks for the test
The nice thing that comes for free from jMock is then the ability to compose custom and standard constraints. For instance:
and(containSlots("a", "b"), not(containSlots("c", "d")))
At the end of the day, unit testing with style is not wasted time. If you consider your unit test code as first-class code, that must be as factored and expressive as possible, you will really facilitate communication, design and maintenance (especially test code maintainance, which can be otherwise very, very, very expensive,...).