Design By Contract testing
The problem:
Recently, I had to write JUnit tests for a large date/calendar class CDate
in our project. This is quite a large piece of code, containing about
one hundred small methods. (We have written our own class because java.util.Date & java.util.Calendar are quite a mess and we also needed some specific, Polish hollidays and other stuff. (And our class is a mess, too.))
** Be part of the Best Java Blogger 2 Contest (May 2005)
CDate has many similar methods that test some date attribute (isMonday(), isThursday()) or get various attributes (getMonth(), getDay()). This posed some problem: how do you test similar methods, assuring that they will undergo the same tests but without unnecessary code duplication? This article presents a solution.
A bad solution
The most simple approach would be just copy & paste test code for each method, adjusting necessary details. But this is bad. This is evil. A maintainer’s nightmare.
Next thing I thought about was reflection. The general idea was to make one generic test method (like checkIsSomeWeekDay()) and have other test methods (testIsMonday(), testIsThursday()) call it, using the method name as a parameter. This would look like this:
private void checkIsSomeWeekDay (int n, String methodName)
throws SecurityException
, NoSuchMethodException
, IllegalArgumentException
, IllegalAccessException
, InvocationTargetException
{
Method method = CDate.class.getDeclaredMethod (methodName, null);
for (int i = 0; i < datesForTest.length; i++) {
CDate date = datesForTest [i];
Boolean is = (Boolean) method.invoke (date, null);
int expectedDayOfWeek = getExpectedDayOfWeek (datesForTest [i]);
Boolean expect = Boolean.valueOf (n == expectedDayOfWeek);
assertEquals (expect, is);
}
}
public void testIsMonday ()
throws SecurityException
, IllegalArgumentException
, NoSuchMethodException
, IllegalAccessException
, InvocationTargetException
{
checkIsSomeWeekDay (Calendar.MONDAY, "isMonday");
}
The code works as follows: for every date in predefined test set (datesForTest) it invokes (via reflection) method specified as parameter. It also gets the tested value from some other method. For every date, they should agree whether it is a Monday or not. This is verified by assertEquals in last statement of the loop. The real test is of course much bigger. I also skipped tests for other week days.
This approach works, but has many drawbacks and limitations:
- Reflection prevents compiler from checking your code. If you change signatures of tested methods you won’t know your tests are broken until your run them.
- You must analyze the code very carefully to see what’s called and how. No browsing tools will help you. (How much time do you need to tell what is the return type of isMonday()? Does it have any parameters? Is it static or instance method?)
- Code looks clumsy and arcane and is tedious to write.
- Tested methods must all have exactly the same signature (or the reflection call will fail at runtime).
The last drawback is the biggest — this approach won’t work for some more advanced methods, as we’ll see shortly (unless we make code much more clumsy and arcane, of course). In the real life example I had some very similar methods but some of them were static and other were instance methods (this was clearly a design flaw, but I wasn’t allowed to make any corrections to CDate until I write tests).
So there must be some better way. There is: using interfaces and anonymous classes instatiations.
A better solution
This is a classic form of adding "another abstraction level", but also can be thought of designing a contract between the generic test method and specific test methods that call it. In the reflection approach the contract was hidden deep beneath the code. Now we move it to the front. The generic method now asks its user (the specific method) for all the things it needs to know instead of extracting them in some black magic rituals.
So what does it look like? Let’s start by redesigning the checkIsSomeWeekDay() method.
private void checkIsSomeWeekDay (WeekDayHelper h)
{
for (int i = 0; i < datesForTest.length; i++) {
CDate date = datesForTest [i];
boolean val = h.getValue (date);
boolean expect = h.getExpected (date);
assertEquals (expect, val);
}
}
This looks a lot nicer. Gone are the tons of exceptions, reflection method invocations and so on. Instead, the methods expectations are clearly documented: it gets a WeekDayHelper object, that has getValue() and getExpected() methods. getValue() is supposed to call some real CDate method to get appropriate value and getExpected() should return, well, expected value for this date. (And if we changed those methods’ return type to Object, we could test with this simple method almost any getter in any class.) WeekDayHelper is an interface that is also all about simplicity:
public interface WeekDayHelper
{
boolean getValue (CDate date);
boolean getExpected (CDate date);
}
This interface can be inner interface or can be moved outside of the test class. I prefer inner interfaces in this case, but it doesn’t make any significant difference.
Anonymous class instantiation
Now, for the last part: how do we call our new checkIsSomeWeekDay() method? This may look a little more convoluted than the previous version, but in the long run, it’s worth it.
public void testIsMonday ()
{
checkIsSomeWeekDay (
new WeekDayHelper() {
public boolean getValue (CDate date)
{
return date.isMonday ();
}
public boolean getExpected (CDate date)
{
return Calendar.MONDAY == getExpectedDayOfWeek (date);
}
}
);
}
What we see in testIsMonday() is anonymous class instantiation. We create a new object of type WeekDayHelper, but it’s an interface so we must provide implementations of all the methods inline (Java allows this syntax since version 1.1). Effectively we create an object of anonymous use-once-and-throw-away class and hand it to checkIsSomeWeekDay(). This syntax is also popular for writing various event handlers and if you have done some GUI programming you most probably have used it.
We can also leverage inheritance to move the getExpected() method to common superclass and parametrize it to avoid code duplication:
public abstract class BaseWeekDayHelper
implements WeekDayHelper
{
private final int weekDay;
public BaseWeekDayHelper (int weekDay)
{
this.weekDay = weekDay;
}
public boolean getExpected (CDate date)
{
return weekDay == getExpectedDayOfWeek (date);
}
}
public void testIsMonday ()
{
checkIsSomeWeekDay (
new BaseWeekDayHelper(Calendar.MONDAY) {
public boolean getValue (CDate date)
{
return date.isMonday ();
}
}
);
}
As we can see, testIsMonday() contains now only what is necessary to call date.isMonday(). The other method is in base class and the only thing it needs, Calendar.MONDAY constant is sent by constructor parameter.
This may seem as a lot of coding overhead in such a simple example, but it’s a tool that’s applicable to much more complicated cases. (And besides, who would like to read an article with thousands lines of code examples?)
A slightly bigger example
Let’s look at a final example that shows how we can create a common test for methods equals(), compareTo(), after() and before().
private interface ComparisonHelper
{
Object compareDates (CDate d1, CDate d2);
Object compareStrings (String s1, String s2);
}
private void checkComparison (ComparisonHelper h)
{
for (int i = 0; i < datesForTest.length; i++) {
CDate d1 = datesForTest [i];
String s1 = datesForTestAsStrings [i];
for (int j = 0; j < datesForTest.length; j++) {
CDate d2 = datesForTest [j];
String s2 = datesForTestAsStrings [j];
assertEquals (h.compareStrings (s1, s2)
, h.compareDates (d1, d2));
}
}
}
public void testAfter ()
{
checkComparison (
new ComparisonHelper() {
public Object compareStrings (
String s1,
String s2
)
{
return Boolean.valueOf (s1.compareTo (s2) > 0);
}
public Object compareDates (
CDate d1,
CDate d2
)
{
return Boolean.valueOf (d1.after (d2));
}
}
);
}
public void testBefore ()
{
checkComparison (
new ComparisonHelper() {
public Object compareStrings (
String s1,
String s2
)
{
return Boolean.valueOf (s1.compareTo (s2) < 0);
}
public Object compareDates (
CDate d1,
CDate d2
)
{
return Boolean.valueOf (d1.before (d2));
}
}
);
}public void testCompareTo ()
{
checkComparison (
new ComparisonHelper() {
public Object compareStrings (
String s1,
String s2
)
{
return new Integer(Util.sgn (s1.compareTo (s2)));
}
public Object compareDates (
CDate d1,
CDate d2
)
{
return new Integer(Util.sgn (d1.compareTo (d2)));
}
}
);
}public void testEquals ()
{
checkComparison (
new ComparisonHelper() {
public Object compareStrings (
String s1,
String s2
)
{
return Boolean.valueOf (s1.equals (s2));
}
public Object compareDates (
CDate d1,
CDate d2
)
{
return Boolean.valueOf (d1.equals (d2));
}
}
);
}A quick analysis: ComparisonHelper interface still contains two methods. The compareDates() method is supposed to call some comparison method on its arguments (like d1.before(d2)) and compareStrings() should return expected value of that comparison (perhaps performing the comparison in some other way).
checkComparison() is the generic test method, it runs two nested for loops to compare each date with each other. We also need an array of strings that represent the same dates to get expected values easily (the strings are in format "YYYY-MM-DD HH:MM:SS", so they can be compared). For each pair of dates, checkComparison() calls compareDates() and compareStrings() of appropriate helper object to verify that underlying CDate method works correctly.
testAfter(), testBefore(), testCompareTo() and testEquals() call checkComparison() with anonymous classes that implement ComparisonHelper and use appropriate methods of CDate. As we can see, CDate.compareTo() returns an int while the other methods return a boolean. This is no problem for us, as I promised earlier.
Recalling list of drawbacks and limitation of reflection approach, we see that none apply:
- Compiler now verifies that all of the method calls are correct. There are only explicit calls.
- Your tests are easily understandable and browsable.
- Code is less clumsy and less tedious, but it may seem at first that there is more code to write. It’s mainly because of Java verbosiveness. We have also explicitly documented our contracts.
- Tested method signatures may differ in any aspect.
Happy coding and testing!
did you mean design by contRact ? i guess so
What does your Runnable do? Why does it call testMonday()? What is accepting this Runnable?
I don’t need to accept a Runnable that runs testMonday(). This is done by JUnit framework and I don’t care how.
My point was to encapsulate the code that would otherwise be copy-pasted from testMonday() to testTuesday() to testWednesday() and so on.
Please be so kind to provide some more complete code that show how to achieve ‘my’ point in ‘your’ way.
Seems very over-designed for me. Why not accept a Runnable that tests the right thing?
Runnable mondayRunnable = new Runnable() {
public void run() {
// invoke testMonday and verify the result
}
}
whats up