◆ Problem
You need to test a method with no return value. You would like to be able to apply recipe 2.2, “Test a method that returns nothing,” but you are unable to change the production code to create an observable side effect. You need an alternative.
◆ Background
Perhaps the most annoying aspect of working with legacy code is that if the original authors did not design for testability, then you have to jump through hoops to write effective tests. In particular, there might be no publicly observable side effect for a given behavior—that is, you want to test a class feature, but its behavior can only be observed from within the class itself. Perhaps only privately accessible data is affected with no way to query that data. In this situation you typically have three options:
1 Make the private data visible by adding a query method.
2 Write a higher-level (or more coarsely grained) test that involves other production objects.
3 Bypass the Java protection mechanism in your tests.
JUnitX provides a way to do the latter, which might be the solution you need.
TE AM FL Y
Team-Fly®
621 Test a legacy method
with no return value
◆ Recipe
Create a PrivateTestCase using JUnitX that allows you to execute non-public methods and gives you access to non-public data. Use this extra power to make assertions about the state of the object before and after you invoke the desired methods. Following are the steps to create a PrivateTestCase:
1 Create a subclass of PrivateTestCase.
2 In the package containing the class under test, create a subclass called TestProxy of junitx.framework.TestProxy. You can find the code for this class in listing 17.3. The code for this class is the same for every package that requires it.
3 Write the test in your subclass of PrivateTestCase, which invokes the method with no return value.
4 Use the PrivateTestCase methods get(), getInt(), getLong(), getBool- ean(), and so on to make assertions about the private data that the method changes.
There is one common design that creates a need to use this technique: the Observer/
Observable pattern as it is often implemented in Java. Even though Java provides its own implementation of this in the java.util package, many programmers feel the need to reproduce this design themselves. Because we are talking about legacy code, let us first consider the code we wish to test, shown in listing 17.10.
package junit.cookbook.patterns;
public class Observable {
private Observer[] observers = new Observer[9];
private int totalObs = 0;
private int state;
public void attach(Observer o) { observers[totalObs++] = o;
}
public int getState() { return state;
}
public void setState(int in) { state = in;
notifyObservers();
}
Listing 17.10 An implementation of Observable
622 CHAPTER 17 Odds and ends
private void notifyObservers() { for (int i = 0; i < totalObs; i++) observers[i].update();
} }
In its current state, the only way to verify that an Observer is correctly attached is to attach one, trigger an update, and then verify that it was correctly notified.
While that does not sound like much to do, it is important to realize the imple- mentation detail you need to know to write that simple test: that the way to trigger an update is to invoke the method setState(). Your test depends on the mecha- nism for notifying observers, even though all you want to verify is that Observable registers your Observer correctly. We ought to be able to test that behavior inde- pendently of the way observers are notified.
Listing 17.11 shows the test that uses JUnitX’s facility for gaining access to private data. We have highlighted the key line of code in bold print. This is the line that uses JUnitX’s get() method to retrieve the value of the private variable observers.
package junit.cookbook.patterns.test;
import junit.cookbook.patterns.Observable;
import junit.cookbook.patterns.Observer;
import junitx.framework.PrivateTestCase;
import junitx.framework.TestAccessException;
public class ObservableTest extends PrivateTestCase implements Observer {
public ObservableTest(String name) { super(name);
}
public void testAttachObserver() throws TestAccessException {
Observable observable = new Observable();
observable.attach(this);
Observer[] observers =
(Observer[]) get(observable, "observers");
assertTrue(arrayContains(observers, this));
}
private boolean arrayContains(
Object[] objects, Listing 17.11 ObservableTest
Self-Shunt pattern
Read private instance variable
Refactor to utility class
623 Test a legacy method
with no return value
Object object) {
for (int i = 0; i < objects.length; i++) if (object.equals(objects[i])) return true;
return false;
}
public void update() { }
}
This test implements the Self-Shunt pattern: the test case class itself implements a required interface—in this case, Observer—to avoid the need to create an anony- mous implementation and use it in the test case. We recommend reading Michael Feathers’ “The ‘Self-Shunt’ Unit Testing Pattern” for details on this useful technique.
For the sake of completeness, listing 17.12 shows the standard implementation of TestProxy. To gain access to non-public parts of a class, you must write a ver- sion of this class in the same package as that class. The class must be named TestProxy—JUnitX’s rules, not ours.
package junit.cookbook.patterns;
import junitx.framework.TestAccessException;
public class TestProxy extends junitx.framework.TestProxy { public Object newInstance(Object[] arguments)
throws TestAccessException { try {
return getProxiedClass() .getConstructor(arguments) .newInstance(arguments);
}
catch (Exception e) {
throw new TestAccessException(
"could not instantiate "
+ getTestedClassName(), e);
} }
public Object newInstanceWithKey(
String constructorKey, Object[] arguments)
throws TestAccessException { Listing 17.12 TestProxy
Intentionally empty
Place in package containing production code
Class must be named TestProxy
624 CHAPTER 17 Odds and ends
try {
return getProxiedClass()
.getConstructor(constructorKey) .newInstance(arguments);
}
catch (Exception e) {
throw new TestAccessException(
"could not instantiate "
+ getTestedClassName(), e);
} } }
◆ Discussion
We have already advised the reader against using non-public parts of a class to write tests. Although there are varying opinions on the matter—as a search of the Web certainly illustrates—we believe that in judging the trade-off between encap- sulation and testability, we lean towards testability. What is the point of a good design if we cannot directly verify the code’s behavior? If we have to choose between working code and well-designed code, we opt for working code, because we know that we can refactor working code. Now with JUnitX, there is another option: we can maintain the production code’s design by writing more compli- cated tests, such as the ones we have seen using JUnitX. We prefer simpler code to more complicated code, and we hold tests to the same standards as production code in this respect. For that reason, we tend to favor good designs that allow for simple tests over (arguably) better designs that require more complicated tests.
You have to live with your own decision here, so do what you think is right.
We recommend the technique in this recipe either as a last resort or as a step- ping-stone towards a more testable design.3 The idea here is to “jam in” the tests you need so that you can feel confident refactoring the design towards something more testable. You might think that adding a query method for the private data you need violates encapsulation “just for testing.” Although we cannot argue with that statement, we believe that making a class easier to test is worth a temporary violation of encapsulation.4 Our typical approach is to break encapsulation, write
3 See recipe 4.7, “Control the order of some of your tests,” for another example of using a testing tool as a temporary refactoring aid.
4 We say “temporary” because there is almost always another well-encapsulated solution to the same prob- lem that is easier to test. We have a wonderful proof, but there is not enough space here to explain it.
625 Test a private method if you must
the tests we need, and then refactor back towards a well-encapsulated solution using the tests as a safety net. We think it’s better than nothing.
We also mentioned the possibility of writing Integration Tests in place of Object Tests in this situation. It might be possible to use a collaborating class to observe the side effect of the behavior you want to test. We prefer not to resort to a less isolated test, because we lose the ability to identify the cause of a problem from the particular test that fails. The decision to treat the legacy system as a black box depends on your intent to change its design. If you plan to replace the legacy system with another implementation (which you test thoroughly with JUnit), then it might be wise to freeze some portion of the legacy system’s API, capture it in an interface, and write tests against the new interface. As you build the replacement system, you can verify its behavior against the interface-level tests to ensure its behavior is consistent with the legacy system. If, however, you plan to refactor the legacy system towards a more testable design, then JUnitX provides you the means to begin creating a refactoring-friendly safety net. We applaud your courage and advise that you proceed with caution.
◆ Related
■ 2.2—Test a method that returns nothing
■ Michael Feathers, “The ‘Self-Shunt’ Unit Testing Pattern”
(www.objectmentor.com/resources/articles/SelfShunPtrn.pdf)