Factor out a test fixture

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

Problem

You have written several tests for the same production class, and they contain duplicate code. Knowing that duplication is the root of all evil in software, you want to remove it.

Background

One of the earliest patterns you see when writing tests for the same production class is that the first few lines of each test look the same. Remember that each test has three basic parts: create some objects, invoke some methods, check the results. The second of these three is usually different for each test; deciding which method to invoke usually identifies the test: “If I call the constructor with these parameters, I expect that result; but if I pass null, then the constructor should throw that kind of exception.” This is the kind of internal dialog—or external, if you’re talking to a Pair Programming partner—that leads to the list of tests you intend to write for a class. Duplication is possible here, but relatively uncommon.

The third part of the test, checking the result, depends entirely on the method you invoke. If the tests invoke different methods, the expected results will also be different. You normally only see duplication here if there is duplication in invok- ing the method.

Creating the object, however, leads to the most duplication from test to test. It is common to have many more methods on a class than constructors, so once you write even a second test for your class, you run the risk of duplicating the “create some objects” part of the first test, because you may well be calling the same con- structor with the same parameters. Because this kind of duplication is so common, it would be nice if JUnit had a built-in mechanism for eliminating it.

Kent Beck calls the objects under test a test fixture: a “configuration” of objects whose behavior is easy to predict.10 Using Kent’s terms, the first part of a test—the

10Kent Beck, “Simple Smalltalk Testing: With Patterns” (www.xprogramming.com/testfram.htm). This is the original paper in which Kent describes SUnit, a Smalltalk-based predecessor to JUnit.

84 CHAPTER 3

Organizing and building JUnit tests

“create some objects” part—can be called “create a fixture.” The goal is to create some objects and then initialize them to some known state so that you can predict their responses to invoking methods on them.

Recipe

Identify the duplicated test fixture code in your tests. Move that code into a new method called setUp(). The resulting code may no longer compile because you are now declaring variables in setUp() and then using them in your tests. Change those variables into instance-level fields so that both setUp() and your tests can refer to them. Because each test executes in its own instance of your test case class, there is no need to worry about instance-level fields being set incorrectly from test to test. When you execute your tests, the test runner invokes setUp() before each test.

Every other test you write in this test case class can use this common test setup.

To illustrate this technique, listing 3.2 shows three Money tests that use the same Money object.

package junit.cookbook.organizing.test;

import junit.cookbook.util.Money;

import junit.framework.TestCase;

public class MoneyTest extends TestCase { public void testAdd() {

Money addend = new Money(12, 50);

Money augend = new Money(12, 50);

Money sum = addend.add(augend);

assertEquals(2500, sum.inCents());

}

public void testNegate() {

Money money = new Money(12, 50);

Money opposite = money.negate();

assertEquals(-1250, opposite.inCents());

}

public void testRound() {

Money money = new Money(12, 50);

Money rounded = money.roundToNearestDollar();

assertEquals(1300, rounded.inCents());

} }

Listing 3.2 MoneyTest before moving code into setUp()

85 Factor out a test fixture

Notice that the first line of each of these three tests is almost identical. Each test uses a Money object representing $12.50, so this object appears to be a candidate to move into setUp(). Because testAdd() calls its object addend and the others call it money, you first need to rename addend to money to make the first line in all three tests identical. Then you can move that line into setUp(). Finally, change money from a local variable to an instance-level field so that setUp() and the test meth- ods can all use it. When you’ve finished, the code looks like listing 3.3, with the new fixture code highlighted in bold print.

package junit.cookbook.organizing.test;

import junit.cookbook.util.Money;

import junit.framework.TestCase;

public class MoneyTest extends TestCase { private Money money;

protected void setUp() throws Exception { money = new Money(12, 50);

}

public void testAdd() {

Money augend = new Money(12, 50);

Money sum = money.add(augend);

assertEquals(2500, sum.inCents());

}

public void testNegate() {

Money opposite = money.negate();

assertEquals(-1250, opposite.inCents());

}

public void testRound() {

Money rounded = money.roundToNearestDollar();

assertEquals(1300, rounded.inCents());

} }

Each test now assumes that there is already a Money object called money with the value $12.50. The test duplication has been removed.

Discussion

JUnit provides direct support for test fixtures through two methods, setUp() and tearDown(), found in junit.framework.TestCase. When you subclass TestCase, you can override these methods to set up and tear down (clean up) the fixture for

Listing 3.3 MoneyTest after moving code into setUp()

86 CHAPTER 3

Organizing and building JUnit tests

each test. To see how JUnit uses these fixture methods, examine the source for runBare(), another TestCase method:

public void runBare() throws Throwable { setUp();

try {

runTest();

}

finally { tearDown();

} }

When it is time to execute your test, the framework invokes runBare(), which sets up your fixture, runs the test, and then tears down your fixture. Notice that by placing tearDown() in a finally block, this method is guaranteed to be called even if the test fails. This is particularly important to avoid the situation where your test initializes some expensive, external resource and then is left unable to clean up after itself. It may leave an open database connection, files on the file system, or something on a network. Executing these tests repeatedly in a short time period may exhaust system resources—yours or someone else’s. It is very impor- tant for a test to tear down any fixture that it sets up. In our example, there was nothing in our fixture to clean up: when the test finishes executing, the underly- ing TestCase object simply goes out of scope and is ready to be garbage collected.

This is why we did not override tearDown(). By default—that is, in TestCase itself—setUp() and tearDown() do nothing.

We would be remiss if we failed to mention one disadvantage to extracting common setup code into a test fixture. The resulting tests use instance-level fields that are initialized in another method, and this level of indirection introduces extra steps for someone trying to read the tests. Rather than reading each test method as a complete “story,” the programmer has to look at setUp(), the field declarations, and the test. One of the benefits of implementing tests as methods is that they are easy to read; however, extracting part of a test’s code into a different method without leaving behind an explicit call to that method can bewilder the programmer trying to read the test. We want to avoid hearing, “Where did these objects come from?!” If you name the fields appropriately, then the test will com- municate effectively. As always, names are important.

To counter this disadvantage, we feel that removing duplication ultimately does more good than the harm that might be done by making the tests slightly less easy to read. We argue that before long, the JUnit practitioner is trained to remember that the framework invokes setUp() before each test and therefore

87 Factor out a test fixture hierarchy

automatically looks for that method when reading a test for the first time. Also, if the tests are sufficiently focused on a single behavior, then they tend to be short.

If the entire test case class fits on one screen, then perhaps it is not so much more difficult to read than the alternative. As always, we recommend that you try both techniques and decide which works better for you, your team, or your project.

Related

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

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

(753 trang)