◆ Problem
You want the setUp() method to execute once for all methods in a TestCase, but the JUnit framework is designed to run setUp() before every single test method is run.
◆ Background
The setUp() method is JUnit’s primary means for setting up test data fixtures in preparation for the execution of a test method (tearDown() is for cleaning up fixtures after a test method completes). Some users are surprised to realize that setUp() is called before every test method. Many users find that while they need setUp() and tearDown() before and after every test method, they also want a meta setUp() that can run once and only once for the whole suite of tests in their TestCase subclass.
Most often this is because they need setUp() to establish a fixture that is expensive to initialize, such as a database connection, a J2EE or JDBC transaction, or an appli- cation deployment.
162 CHAPTER 5
Working with test data
On one project, we were looking for ways to optimize one of our large JUnit- based test harnesses and make it run faster. After a code review in which we exam- ined several test cases, we found a great deal of database initialization code in the setUp() and tearDown() methods of test cases that were contributing to overhead in terms of additional minutes spent negotiating with the database.
Some application servers provide a deployment API that you can call to have your application deployed programmatically. Many J2EE application servers sup- port hot deployment and redeployment of applications just by copying deploy- able files to certain directories (which is easy enough to do programmatically from within a JUnit test). If your JUnit test is a kind of integration test where it needs to call out to an EJB or a servlet in a J2EE server automatically at test time, it is nice to control the deployment of the application containing the EJB or servlet from within your test. But deploying an application usually takes several seconds and isn’t usually designed for high performance, so deploying and undeploying between every test method becomes slow and may uncover stress-related bugs in the application server’s hot deployment feature. Database connections are rela- tively expensive to acquire, especially if you are not using a DataSource or connec- tion pool but are reloading the database driver and getting a new connection between every single test method call.
To understand this recipe, you should first know what a Decorator is. A Decora- tor “wraps” itself around another object that implements the same interface it does. The Decorator effectively intercepts method invocations to the “wrappee,”
adding some extra behavior as desired. It is one of the structural patterns in the Design Patterns book, and is also known as Wrapper.
◆ Recipe
Use junit.extensions.TestSetup to do one-time setUp() in your TestCase so that setUp() and tearDown() in TestSetup get called once per test class rather than once before and after each test method.
The typical way to use this is as follows:
1 Implement a custom suite() method in your TestCase.
2 Create an anonymous class that extends TestSetup.
3 Implement setUp() and tearDown() inside the anonymous class.
4 Return an instance of your subclass of TestSetup at the end of the suite() method.
163 Set up your fixture once
for the entire suite
Listing 5.7 is a code example of our ongoing InterestCalculatorTest class, now modified to load its properties data file once by using a TestSetup Decorator in the suite() method.
package junit.cookbook.testdata;
import java.io.IOException;
import java.util.ResourceBundle;
import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class InterestCalculatorTestWithTestSetup extends TestCase { private static double interestRate;
private static double loanAmount;
private static int loanDuration;
static final String baseName
= "junit.cookbook.testdata.InterestCalculatorTestWithTestSetup";
public static Test suite() { TestSuite testSuite = new TestSuite(
InterestCalculatorTestWithTestSetup.class);
TestSetup wrapper = new TestSetup(testSuite) { public void setUp() throws IOException { ResourceBundle rb =
ResourceBundle.getBundle(baseName);
interestRate =
Double.parseDouble(rb.getString("interest.rate"));
loanAmount = Double.parseDouble(
rb.getString("principal.amount"));
loanDuration = Integer.parseInt(
rb.getString("loan.duration.years"));
} };
return wrapper;
}
public void testInterestCalculation() { // fake interest calculation }
}
Listing 5.7 InterestCalculatorTestWithTestSetup
Create the TestSuite
Invoked once for the entire suite
Wrap a TestSetup around the TestSuite
164 CHAPTER 5
Working with test data
Note that this code uses a ResourceBundle (see recipe 5.5) for the data that is to be loaded. You rename the properties file from that recipe’s example to Inter- estCalculatorTestWithTestSetup.properties and then copy it to the directory in your class path that matches the package structure of the InterestCalculator- TestWithTestSetup (that is, junit/cookbook/testdata within your source tree).
◆ Discussion
One small disadvantage to this technique is that any variables you need to share between TestSetup in the suite() method and the rest of the TestCase have to be declared static because suite() is static.
Finally, we should mention one flaw in JUnit’s implementation of TestSetup. When the wrapped test suite fails, TestSetup.tearDown() is not invoked, meaning that the shared fixture is not properly cleaned up. Much of the time this is not a grave concern; however, if you need ensure that the shared fixture is cleaned up properly at the end of the test suite, see recipe 16.5, “Ensure your shared test fix- ture tears itself down,” which explains how to use an alternate implementation of TestSetup, found in JUnit-addons.
◆ Related
■ 5.6—Use a file-based test data repository
■ 16.5—Ensure your shared test fixture tears itself down