Wednesday, August 5, 2009

Another class of Bugs that Are Hard to Unit Test

Imagine the set of classes listed at the bottom of this post. The point is that we're performing a calculation using an expensive test, but when using a derived class we can make use of an inexpensive test. Both tests will give the same result but clearly its better to use the inexpensive test if we can. How can we write a unit test that verifies that we've used the inexpensive test (and used it correctly)?

Several possibilities come to mind. One option is to write a unit test that times the call to the method. This is pretty clearly not a good idea as it leaves the results hostage to the load on test system.

Other approach is to embed a flag in the class that gets raised when the expensiveTest method is called. That's not a terrible idea but it does put test code in the actual production code. A slightly better idea is to create a test class that extends BaseThing and implements the flag in its own version of expensiveTest.

@Test testQuickReject() {
DerivedClass foo = new DerivedClass () {
boolean flag = false;
boolean expensiveTest() {
flag = true;
return super.expensiveTest();
}
};

foo.pickBestThing();
assert(flag == true);
}

This leads to an observation about the effect of testing on code design. The approach being taken only works if the expensive test is a separate and thus overridable method. If the code representing the expensive test was just a bunch of inline code then there would be nothing for the test class to override. One could certainly go back and to a Refactor:ExtractMethod on the code, but if the class had been designed with testing in mind from the start that would not be necessary.


class BaseThing {
Thing pickBestThing(List thingList) {
Thing bestThing = null;

for(Thing thing : thingList) {
if(quickReject(thing)
continue;

if(expensiveTest(thing))
bestThing = thing;
}
return(thing);
}

boolean quickReject(Thing thing) {
return false; // no-op method
}
}

class DerivedClass extends BaseThing {
boolean quickReject(Thing thing) {
// some real test that only applies in the derived class case
}
}

No comments:

Post a Comment