6.5 Reload classes before each test
◆ Problem
You want to execute each test with freshly loaded classes as a way to cope with pro- duction code that sets global data at startup.
◆ Background
In recipe 6.4, “Execute each test in its own JVM,” we described the general prob- lem: some of the classes involved in a test use global state and provide no direct way
183 Reload classes before each test
to reset that state. One approach is to expose that state so that we can reset it as needed; however, that involves changing the code before we begin to test it. We would rather install some tests before we change the code so that we have some con- fidence that we have not changed the code’s behavior in some unexpected way. In the previous recipe we described using Ant to execute each test in its own JVM, but that is quite a heavyweight solution that can cause the tests to execute quite slowly.
We would like a solution that has the benefits of executing each test in its own JVM (freshly loaded classes) without resorting to starting and stopping so many JVMs.
◆ Recipe
After involving himself in a mailing list discussion on the topic, Neil Swingler decided to build a simple solution to this problem: a ReloadedTestCaseDecorator that reloads classes before each test. You wrap each test in this decorator then exe- cute the resulting test suite. Each test in the suite executes with freshly loaded classes. To illustrate this technique, let us try to test an object cache. Suppose you have an object directory that retrieves objects from a database or a network con- nection—each time you look up the same object, you incur unnecessary over- head, because the objects you retrieve never change. You want to add a cache onto the directory to avoid invoking the expensive object lookup() method more than once for each differently named object. Because you only need one cache, you decide to implement it as global methods and data. To be sure that your ObjectCache is actually caching the result, you want to verify whether cache hits occur when you expect them to. You start with the following tests:
package junit.cookbook.running.test;
import junit.cookbook.running.*;
import junit.framework.TestCase;
public class ObjectCacheHitTest extends TestCase implements Directory { protected void setUp() throws Exception {
ObjectCache.directory = this;
}
public void testFirstLookup() throws Exception { assertEquals("there", ObjectCache.lookup("hello"));
assertEquals(0, ObjectCache.countCacheHits());
}
public void testExpectingCacheHit() throws Exception { assertEquals("there", ObjectCache.lookup("hello"));
assertEquals("there", ObjectCache.lookup("hello"));
assertEquals(1, ObjectCache.countCacheHits());
}
184 CHAPTER 6 Running JUnit tests
// Self-Shunt method
public Object get(String name) { return "there";
} }
Here we use the Self-Shunt pattern and let the test case class itself be the Direc- tory. Even though its implementation of get() does not perform some expensive lookup operation, the production Directory will, but this particular implementa- tion detail does not concern us right now. We have two tests, one that expects no cache hit on the first request to retrieve an object, and another test that retrieves the same object twice, expecting only one cache hit. The problem is that when you execute these tests, the second fails.
junit.framework.AssertionFailedError: expected:<1> but was:<2>
It seems that in the second test both invocations of get() resulted in cache hits, and not just the second one. This is the problem with Singletons: you need to reset the ObjectCache’s state before executing the second test; otherwise, it “inherits” what- ever state the previous test left behind. Fortunately for us, ReloadedTestCase- Decorator comes to the rescue. To use this utility, you need to create a test suite
“by hand” for your test case class (see recipe 4.2, “Collect a specific set of tests”), wrapping each test in the ReloadedTestCaseDecorator. We can approximate this well enough for most purposes by adding this suite() method to our test case class.1
public static TestSuite suite() { TestSuite suite = new TestSuite();
Method[] methods = ObjectCacheHitTest.class.getMethods();
for (int i = 0; i < methods.length; i++) { Method method = methods[i];
String methodName = method.getName();
if (methodName.startsWith("test")) { suite.addTest(
new ReloadedTestCaseDecorator(
ObjectCacheHitTest.class, methodName));
} }
return suite;
}
1 Strictly speaking, this suite() method will include methods that may not be valid tests, but it is good enough for most purposes.
185 Ignore a test
When we add this suite() method to our test case class, all the tests now pass!
Each test executes against a freshly loaded ObjectCache with an empty cache. You can now add more tests without worrying about the state of the ObjectCache at the end of the previous test, just as if you instantiated a new one each time. This sim- plifies the tests considerably and does not require changing the class under test.
◆ Discussion
Neil’s solution uses a bytecode manipulation library called Javassist2, part of the JBoss application server project. The ReloadedTestCaseDecorator instantiates each test after first reloading the test class. You can find a current version of Neil’s code in the files section of the JUnit Yahoo! group.3 The only real downside to this approach is that you need to decorate any test that needs to reload its fixture classes. We called this a downside only because it involves work; otherwise, we think it is a good thing: if your tests are going to do something as drastic as reload classes, then you want to know that it is happening—you want to intend to do it, and not just have it happen as a matter of course. The suite() method we provide in the example is essentially a universal one: simply put it in a utility class and use it wherever you need it.
◆ Related
■ 4.2—Collect a specific set of tests
■ 6.4—Execute each test in its own JVM
■ The Javassist project (www.jboss.org/developers/projects/javassist.html)