Test a private method if you must

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 656 - 661)

Problem

You would like to test a private method and prefer not to or are unable to make the method public “just for testing.”

Background

Not everyone agrees with the philosophy of testing classes entirely through a public interface. We respectfully agree to disagree. You have a private method that is com- plex enough to warrant its own tests, but nevertheless is not important enough to promote to the public interface or refactor to a collaborating class. You could test the method indirectly through the public methods that invoke it, but you would rather test it in isolation, which is a laudable goal.

626 CHAPTER 17 Odds and ends

It is also possible that you’ve been painted into this corner, inheriting code that was not designed to be tested. In that case, your goal might be to create a refactor- ing safety net before attempting to move code around. In that case, you might have no choice but to test the private method before deciding how to refactor the class.

Recipe

JUnitX provides the ability to gain access to private methods. See recipe 17.5,

“Test a legacy method with no return value,” for instructions on how to enable JUnitX to access the private parts of the class you want to test. After you have done that, invoke the method invoke() which is described in table 17.1.

We can return to the example from recipe 17.5. In the previous recipe, we used JUnitX to gain access to the private variable observers to verify that the method attach() works. Here we invoke the private method notifyObservers() to verify that it indeed notifies its observers. Here is the test, which uses EasyMock to implement a Spy Observer:

public void testNotifyListeners() throws Exception { MockControl observerControl =

MockControl.createControl(Observer.class);

Observer observer = (Observer) observerControl.getMock();

observer.update();

observerControl.setVoidCallable();

observerControl.replay();

Observable observable = new Observable();

observable.attach(observer);

invoke(observable, "notifyObservers", new Object[0]);

observerControl.verify();

}

Table 17.1 Parameters to JUnitX’s PrivateTestCase.invoke() method

Parameter Description

Object object The object on which to invoke the method String methodKey The name of the method to invoke

Object[] arguments An array of the arguments to pass to the method. The array must be the same length as the number of parameters the method expects.

627 Test a private method if you must

We have highlighted in bold print the line of code that uses JUnitX to invoke the private method notifyObservers() with no parameters. This test passes, telling us that Observable works with a single observer. We could next attach the observer more than once, and then expect the observable to invoke update() more than once. With EasyMock, that is easy.

public void testMultipleListeners() throws Exception { MockControl observerControl =

MockControl.createControl(Observer.class);

Observer observer = (Observer) observerControl.getMock();

observer.update();

observerControl.setVoidCallable(5);

observerControl.replay();

Observable observable = new Observable();

for (int i = 0; i < 5; i++) observable.attach(observer);

invoke(observable, "notifyObservers", new Object[0]);

observerControl.verify();

}

Using EasyMock, we say that we expect observer.update() to be invoked five times. Next, we attach the same observer five times to the observable, in order to receive five notifications. This test also passes, so we can be certain that the observ- able supports five observers. Change five to whatever number you like, if you feel you need to test further. We are satisfied and stop here. The key to this recipe is seeing how to use JUnitX to invoke private methods. If you must do it—and we recommend against it—then at least you know an easy way to do it.

Discussion

So what’s so bad about testing private methods, anyway? Perhaps the greatest problem is that private methods are private for a reason: the author intended for nothing but this one class to have any knowledge of the particulars of its implementation. Most notably, private methods might change—both their behavior and their interface—with no expected impact to any other part of the system. That is the power of private methods.5 In that sense, private methods support refactoring very well by providing the design with a degree of freedom of

5 It is interesting, of course, that this notion of privacy is not universal and that other languages get along swimmingly without it. It must be a matter of taste, because somehow people manage to write good soft- ware in Smalltalk.

628 CHAPTER 17 Odds and ends

change. When we invoke a private method directly, though, we destroy this degree of freedom. With each test we add for a private method, we introduce a dependency: when the production code changes, the test has to change. In this way, the private method introduces the same kind of dependency that a public method introduces: if the method changes, then the tests must change. You might as well make the method public at that point.

You can achieve private/public access control by placing public methods on an interface and private methods on an implementation, much the way that an EJB is designed. If you want to test a particular implementation of the interface, then write tests for that implementation class; and if you want to test adherence to the behavior of methods on the interface, then introduce an Abstract Test Case (See recipe 2.6, “Test an interface”). Client code uses each implementation only through its interface, restricting the methods it can invoke while allowing you to test the implementation details as thoroughly as you need. In a sense, we can do away with private/public access entirely by introducing the appropriate inter- faces. We don’t recommend changing all your code tomorrow, but this is an idea worth considering for new code.

Related

■ 2.6—Test an interface

■ 17.5—Test a legacy method with no return value

629

Complete solutions

630 APPENDIX A Complete solutions

Here you can find complete solutions to some of the problems we raise in the rec- ipes. We did not want to confuse these recipes with code samples that are hun- dreds of lines long, but we thought it important to include the complete solutions, so we have done so here. Consult table A.1 to see the recipes to which these solutions correspond.

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 656 - 661)

Tải bản đầy đủ (PDF)

(753 trang)