■ 3.7—Move special case tests to a separate test fixture
■ 5.10—Set up your fixture once for the entire suite
3.5 Factor out a test fixture hierarchy
◆ Problem
You have multiple test fixtures that share some common objects. These objects are duplicated in the various TestCase classes that implement your fixture. You would like to reuse these objects, rather than duplicate them.
◆ Background
This problem arises most frequently when writing Customer Tests or End-to-End Tests—that is, tests that target the entire system, rather than a single class. On occa- sion, JUnit users—the ones who focus almost entirely on JUnit as a Test-Driven Development tool—forget the programmers out there who are not Test-Driven Development practitioners who nonetheless use JUnit because it provides an easy- to-use Java framework for writing tests.11 We mention this because a Test-Driven Development practitioner would say, “If you have such large fixtures, then your tests are too large. Change your tests so that they focus on a smaller piece of the sys- tem. If you do that, then your problem will go away.” They are right, but that answer is like a typical mathematician’s answer: focused, accurate, and useless.12
If you can make your fixtures smaller or if you can identify parts of your fixture that not all your tests actually share, then so much the better: we prefer a simpler test case hierarchy to a more complex one. If you cannot make things any sim- pler—or at least cannot see how to do it yet—then you should at least try to move
11On occasion the authors themselves are guilty of this, although we are starting to catch on. There is hope.
12It’s an old joke, and possibly not a good one, but the tradition must live on.
88 CHAPTER 3
Organizing and building JUnit tests
the common fixture to one place; and because you’re reading this recipe, that must be what you want to do.
◆ Recipe
The secret to solving this problem is simple: even though you are writing tests, those tests are still implemented by methods and objects, so treat them as meth- ods and objects. One way to extract duplicate behavior in a group of classes is to create a class hierarchy by applying the refactoring Extract Superclass [Refactoring, 336]. In case you do not have Martin’s book handy, here is a step-by- step approach:
1 Select two test case classes that have fixture code in common.
2 Create a new subclass of TestCase. This becomes the superclass for the test case classes with overlapping test fixtures. In this recipe, we call this new class BaseFixture, but you should name it something more meaningful.
3 Declare BaseFixture as an abstract class. There’s no reason to create instances of this class.
4 Change your test case classes so that they inherit from BaseFixture rather than directly from TestCase.
5 Copy the overlapping fixture into BaseFixture—that is, copy the fields and the code in setUp() that initializes those fields. You likely need to change the fields, declaring them as protected rather than private; oth- erwise, the test case classes cannot use them. You could encapsulate the fields in protected get methods, but in this case we think that’s extra code for no reason.
6 From each test case class, remove the fixture fields and the code from setUp() that you moved into BaseFixture.
7 Add super.setUp() at the beginning of each test case class’s setUp() method.
Now you can rebuild and rerun your tests to verify that their behavior has not changed. You can repeat these steps for as many test case classes as you have that share common fixture objects.
After you verify that you have not introduced any errors during these steps, look at the setUp() methods and see whether any could be eliminated because of being empty or containing only super.setUp().
89 Factor out a test fixture hierarchy
◆ Discussion
We can summarize this recipe by saying, “Extract a superclass of your test case classes, declaring it abstract because there is no reason to instantiate it.” If we could be certain that everyone had read Martin’s excellent work, we could cer- tainly have done that. Extracting a Super Fixture, to give this recipe a snappier title, embodies the spirit of this recipe by combining these two key points:
■ It is a good idea to eliminate duplicate test fixture code by moving it up into a superclass.
■ We often do not see the duplication until it happens.
This second point is the reason that this recipe’s title mentions factoring out a test fixture hierarchy rather than building it in. If we knew exactly where all the dupli- cation would be, then we would avoid it; however, we have observed that such clairvoyance is out of our grasp. Instead, we content ourselves to eliminate dupli- cation as soon as we find it. This recipe is just another tool in the toolbox for man- aging design complexity.
When deciding whether to use this recipe, consider the amount of duplication in your test fixtures. While your test fixtures need not be identical to use this rec- ipe effectively, there needs to be enough duplication to warrant the complexities of introducing a new class and moving fields around. Sometimes it is the tests, and not the fixture code, that are duplicated, as in recipe 2.6, “Test an interface.”
Although the compiler is unaffected by the complexity of your test class hierarchy, humans are: other programmers need to be able to read your test code. You may even find it difficult to navigate your own complex test case hierarchies after a few months away from the code. While some people feel that eliminating all duplica- tion is the One True Path, remember that eliminating duplication is only a rule.
If, after thoughtful reasoning, you have decided to break the rule, then by all means go ahead. If breaking the rule lands you in trouble, try to remember which rule you broke so that you can learn from your mistake.
One more thing: remember to call super.setUp() and super.tearDown(), as necessary. When you subclass TestCase directly, you do not need to worry about this, because the superclass implementations do nothing; but now you may have superclass implementations of each method that do something very important!
This is a common mistake—one we will all continue to make until the end of our programming days—so don’t feel bad about it. Instead, see chapter 8, “Trouble- shooting JUnit,” for other common mistakes when writing JUnit tests.
90 CHAPTER 3
Organizing and building JUnit tests
◆ Related
■ 3.4—Factor out a test fixture