■ 4.6—Separate the different kinds of test suites
3.3 Separate test packages from production code packages
◆ Problem
You do not want your tests to have intimate knowledge of the classes under test.
You find it difficult to navigate your source tree because the production classes are interspersed with test classes.
◆ Background
The easiest way to organize your tests is to place them directly alongside your pro- duction code, in the same package and source tree. If you do this, all the code you need to compile is in one directory structure, simplifying your build process. Tak- ing this approach also ensures that you build your tests at the same time that you build your production code, avoiding an entire set of problems. This seems fine until you begin (or someone else working on the same project begins) to write tests that “know too much” about the implementation of the code they are meant to test. This trap is easy to fall into, because the compiler does not complain when
80 CHAPTER 3
Organizing and building JUnit tests
you do this. You have this problem if, as you refactor the production code, you find yourself continually changing these tests, even in response to purely internal changes upon which no other class should depend. This is not only a waste of time but also a source of frustration, and it flies in the face of the object-oriented principle of encapsulation. You need a way to improve the situation so that the tests do not have too much access to the production code.
◆ Recipe
We recommend placing your tests in a different package than the production code under test. This way, the compiler detects any attempt by the tests to use the non-public parts of the classes under test, forcing the tests to use only those parts of the production code intended to be exposed to the outside world.
Placing your tests in a separate package from the production code creates a test package hierarchy. This hierarchy should mimic your production code package hierarchy: create one test package for each production code package so that the test package tree has the same shape as the production code package tree.
Assuming you apply this technique, the last details to consider are how to name your test packages and where to place them. There are two prevailing conventions:
■ For each production code package, add the subpackage test to the end of the production code package name to get the name of the corresponding test package. To test the Java collections framework, you create a test pack- age named java.util.test.
■ Create a top-level package named test, and then place all test packages inside this top-level package, with each test package otherwise named the same as the corresponding production code package. To test the Java I/O libraries, you create a test package named test.java.io.
As with most naming conventions, it matters little which you choose, so long as everyone on the project applies it consistently.
◆ Discussion
It is interesting to see what happens when you place your tests in a different pack- age than your production code. As a result, your tests are forced to use the code under test through its public methods. The jury is still out whether using “out- sider” tests leads to better or worse designs—there are arguments to support each point of view—but there is one simple argument in its favor. When you test code only through public methods, the tests better reflect the way that clients use that
TE AM FL Y
Team-Fly®
81 Separate test packages
from production code packages
code, which helps identify significant design problems—ones that will actually affect the way others use the code.7
As you begin testing behavior, rather than methods, your design will tend to show certain characteristics. In particular, there will be a public method for each operation, representing an “entry point” for exercising the behavior. For a collec- tion, these operations include add() and remove(). Behind this public method there may be non-public methods that the former invokes to help do its work. For a collection, these “helpers” may include resizing a dynamically sized collection when the underlying data structure runs out of space. This is part of the add() behavior but not necessarily a method you expect the outside world to invoke.
Typically, you extract blocks of code and place them in private methods. There will come a time during the testing of a complex bit of behavior that you will want to test one of those private methods on its own.
If you want to write a test for that private method, the design may be telling you that the method does something more interesting than merely helping out the rest of that class’s public interface. Whatever that helper method does, it is complex enough to warrant its own test, so perhaps what you really have is a method that belongs on another class—a collaborator of the first. If you extract the smaller class from the larger one, the helper method you want to test becomes part of the newly extracted class’s public interface, so it is now “out in the open”
and visible to the test you are trying to write. Moreover, by applying this refactor- ing, you have taken a class that had (at least) two independent responsibilities and split it into two classes, each with its own responsibility. This supports the Single Responsibility Principle of object-oriented programming, as Bob Martin describes it.8 You can conclude that having tests in a separate package helps separate responsibilities effectively, improving the production system’s design.
On the other hand, it may be necessary to add methods to a class’s public inter- face just for testing. Consider a class that acts as an event source. The typical Java event/listener design implies that an event source has methods for registering and de- registering listeners, usually called addBlahListener() and removeBlahListener(), where Blah is the kind of event this event source generates. Typically there is no way to query the event source for its event listeners, because only the event source needs to know who might be listening for its events. In spite of this, you may want to write a test that verifies that addBlahListener() correctly adds the BlahListener.
7 Thanks to Roger Cornejo for reminding us of this simple but salient point.
8 Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices. Prentice-Hall, 2002.
82 CHAPTER 3
Organizing and building JUnit tests
At this point you have two options:
■ Your test adds the listener and then asks the event source, “Do you have the listener I just added?”
■ Your test adds a Spy listener, asks the event source to generate an event, and then verifies that your Spy listener “heard” the event.
Certainly the second option is much more complex than the first, a design trade- off we discuss in recipe 14.5, “Test an Object Factory.” The first option, however, requires adding a method such as containsBlahListener() that answers the ques- tion, “Do you have this listener?” If your test were in the same package as the event source, you could make the event source’s internal collection of listeners protected or give them package-level visibility, allowing the test to query the col- lection without polluting the public interface with a method that only the tests need. You can conclude that having tests in a separate package weakens the pro- duction system’s design.
Which is the right answer? As usual, there is no definitive right answer to this question. Test-Driven Development practitioners typically argue that there is no such thing as “just for testing,” but rather that having comprehensive tests is important enough to warrant adding whatever methods are necessary to a class’s public interface to make the class more testable. A testable design is usually a good design. Still, if half a class’s public interface consists of methods that only the tests use, then it is possible that another design problem is crying out to be solved.
Deciding whether this is the case requires judgment that typically comes only from experience. In programming, as in life, practice makes perfect.
NOTE If you use IBM’s VisualAge for Java (VAJ), it may be necessary to place your tests in a different package than your production code. If you want your test code in a different project than your production code, then you must place your tests in a different package, because VAJ does not support mul- tiple projects containing packages with the same name. Unless you want to place your tests and production code in the same VAJ project, write a script to “strip out” the tests during packaging; you have no choice but to move tests to a separate package.9
9 See http://c2.com/cgi/wiki?OrganizeJavaUnitTests.
83 Factor out a test fixture
◆ Related
■ 3.1—Place test classes in the same package as production code
■ 14.5—Test an Object Factory
■ Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices.
Prentice-Hall, 2002.