◆ Problem
You have inherited legacy JDBC code and would like to test it against a live database.
◆ Background
If you have decided to test your legacy JDBC code against a live database, be sure you own the data. See recipe 10.7 for a discussion of the issues involved. Next, you need a mechanism to cope with the complexity of test data setup. We have tried setting up test data using JDBC code itself, and our experience was forgettable. We needed to maintain so much code just for setting up fixtures that it was clearly an unworkable situation. Fortunately we have learned from the experience, and we would like to pass that wisdom on to you.
◆ Recipe
For this recipe we will assume that you want to test your JDBC code as is without refac- toring and that you have a test database at your disposal. The approach is straightfor- ward: create a data set for each suite of tests you would like to execute, and then use DbUnit (http://dbunit.sourceforge.net) to organize that test data on the file system.
DbUnit provides the ability to store test data in simple file formats. This allevi- ates the need to duplicate JDBC code just to set up the database with data, because you only need one copy of the test data file. You can specify that data in XML doc- uments or build up a dataset with code inside your test. Here is an example using the “flat XML format”—that is, a simplified XML format where each tag represents a table and the attributes represent column names.
<?xml version="1.0" ?>
<dataset>
<catalog.beans productId="000"
coffeeName="Sumatra"
unitPrice="750" />
<catalog.beans productId="001"
coffeeName="Special Blend"
unitPrice="825" />
<catalog.beans productId="002"
coffeeName="Colombiano"
unitPrice="925" />
</dataset>
TE AM FL Y
Team-Fly®
361 Test legacy JDBC code with the database
This small example shows specifying three coffee products. Each product is a row in the table catalog.beans with the columns productId, coffeeName, and unit- Price. Your dataset is not limited in its size or complexity in any way, except (of course) by your ability to understand it.
To use a DbUnit dataset in a JUnit test, you can write a test case class that extends org.dbunit.DatabaseTestCase, and then override two methods to help the framework extract your data: getConnection(), which returns a connection to your database; and getDataSet(), which returns the description of your dataset.
Before executing each test in your test case class, DbUnit populates the database with exactly those rows in your dataset. Listing 10.17 shows the DatabaseTestCase to go with the example dataset.
public class FindProductsTest extends DatabaseTestCase { private DataSource dataSource;
private JdbcResourceRegistry jdbcResourceRegistry;
public FindProductsTest(String name) { super(name);
}
protected void setUp() throws Exception {
jdbcResourceRegistry = new JdbcResourceRegistry();
super.setUp();
}
protected void tearDown() throws Exception { jdbcResourceRegistry.cleanUp();
super.tearDown();
}
private DataSource getDataSource() { if (dataSource == null)
dataSource = CoffeeShopDatabaseFixture.makeDataSource();
return dataSource;
}
private Connection makeJdbcConnection() throws SQLException { Connection connection = getDataSource().getConnection();
jdbcResourceRegistry.registerConnection(connection);
return connection;
}
protected IDatabaseConnection getConnection() throws Exception { Connection connection = makeJdbcConnection();
return new DatabaseConnection(connection);
} protected IDataSet getDataSet() throws Exception { Listing 10.17 A test case using a DbUnit dataset
DbUnit methods
362 CHAPTER 10 Testing and JDBC
return new FlatXmlDataSet(
new File("test/data/datasets/findProductsTest.xml"));
} public void testFindAll() throws Exception {
Connection connection = makeJdbcConnection();
CatalogStore store = new CatalogStoreJdbcImpl(connection);
Set allProducts = store.findAllProducts();
assertEquals(3, allProducts.size());
} }
For a discussion of the JdbcResourceRegistry see recipe 10.4, “Verify your tests clean up JDBC resources.” The key methods are getDatabaseConnection(), which asks our data source for a connection, and getDataSet() which loads the dataset from disk in “flat XML” format. We recommend storing your datasets on disk if the corresponding code to build a DefaultDataSet would exceed, say, ten lines.23 The forces that are in conflict are the desire to have the test data in the test and a conflicting desire to keep “noise” out of the test. Although test data is decidedly not noise, the code you need to write to express it may well be, so as always, we rec- ommend that you try both and measure the difference. We think you will end up on the side of pushing all but the simplest datasets to disk.
◆ Discussion
If you currently have tests that set up test data using JDBC code, we recommend you change one of those tests to use DbUnit, then compare the two approaches. It should be clear that DbUnit is the way to go, especially when you consider the impact of not being able to refactor the JDBC code under test. The only way to avoid duplication between the test setup code and the JDBC code under test is to expose the production code’s SQL statements to your test classes, but if you are unable to refactor the code under test then there is no direct way to make those SQL statements available. This forces you to duplicate this SQL code, and the accompanying JDBC code, in your tests! It is certainly not worth the effort.
NOTE DbUnit Limitation—If you have auto-increment or IDENTITY columns on your database tables, you may need to disable those before using DbUnit to prime your tables with data. At press time, DbUnit only supported IDENTITY columns on MS SQL Server. For details, consult the DbUnit site’s FAQ section.
23We recommend consulting the DbUnit site for examples of building a DefaultDataSet in your test.
363 Use schema-qualified tables
with DbUnit
◆ Related
■ 10.4—Verify your tests clean up JDBC resources
■ 10.7—Manage test data in a shared database
■ DbUnit (http://dbunit.sourceforge.net)