Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

59
Unit-Testing Database Operations with DBUnit Clark D. Richey, Jr., Principal Technologist, Mark Logic James Morgan, Senior Software Engineer, SRA TD-5859

Transcript of Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

Page 1: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

Unit-Testing Database Operations with DBUnit

Clark D. Richey, Jr., Principal Technologist, Mark LogicJames Morgan, Senior Software Engineer, SRA

TD-5859

Page 2: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 2

Learn how to effectively unit test your data access code using DBUnit

Page 3: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 3

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 4: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 4

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 5: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 5

Why Unit Test Data Access Code?

Data access code is still code• Always unit test code, right?

Need a way to validate against a variety of dataPersistence frameworks (JPA, JDO, etc) help but…• Frameworks can be misconfigured• Frameworks can be asked to do the wrong thing

• Annotate wrong class• Incorrect mappings

Page 6: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 6

Testing with a Persistence Framework

Trust that the framework itself worksVerify proper usage of the frameworkNo different than testing JDBC based codeAs with most good unit tests, the implementation of the method to be tested is independent from the unit test

Page 7: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 7

Good Unit Tests Are…

Automatic

You don’t have to manually inspect the results;your framework tells you if the tests pass or fail

Page 8: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 8

Good Unit Tests Are…

Thorough

Operations are tested against varying data to ensure that no side effects occur (e.g. existing rows are unaffected by an insert)

Operations are tested against a database that is in various states (e.g. tests against empty tables)

Verify that transactions work as expected

Page 9: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 9

Good Unit Tests Are…

Repeatable

Execute the same test against the database multiple times with the same result

The database must be put into a known state prior to each test

Page 10: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 10

Good Unit Tests Are…

Independent

Previous tests don’t affect the currently executing test

The database must be put into a known state prior to each test

Page 11: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 11

Good Unit Tests Are…

Professional

Test code is of the same quality as the business code

Page 12: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 12

Unit Testing Best Practices

Create multiple databases per developer• One for development (db name_dev)• One for testing (db name_test)

Ensure the state of the database prior to testingTest in small chunks of data• Don’t try to load everything into the database for a single test

Page 13: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 13

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 14: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 14

JUnit Test Outline

Setup the database connectionInitialize the test database with test dataExecute the code to be testedThoroughly test the results• Make sure to verify that there are no unwanted side effects

Cleanup the database connections

Page 15: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 15

Database Housekeepingprivate Connection connection;private EmployeeJDBCPersistence persistence;@Before public void setup() { persistence = new EmployeeJDBCPersistence();}private void initializeConnection() throws Exception { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection(url);}

Page 16: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 16

More Database Housekeepingprivate void emptyDatabase() throws SQLException { Statement statement = connection.createStatement(); statement.executeUpdate("delete from employees"); statement.close();}

Page 17: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 17

Setup the Datapublic void testUpdateEmployee() throws Exception { initializeConnection(); emptyDatabase(); Statement insertStatement = connection.createStatement(); insertStatement.execute("insert into employees values('[email protected]', 'Bob', 'Smith')"); insertStatement.execute("insert into employees values('[email protected]', 'Sally', 'Smith')"); insertStatement.execute("insert into employees values('[email protected]', 'Ron', 'Fink')"); insertStatement.close();

Page 18: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 18

Perform the Update

Employee testEmployee = new Employee("[email protected]", "Sally", "Fink");persistence.updateEmployee(testEmployee);

Page 19: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 19

Test the ResultsStatement selectStatement = connection.createStatement();ResultSet employeeResults = selectStatement.executeQuery("Select * from employees where email = '[email protected]'");assertThat(employeeResults.next(), is(true));

Page 20: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 20

Keep Testing the ResultsString email = employeeResults.getString("email");String firstName = employeeResults.getString("firstName");String lastName = employeeResults.getString("lastName");assertThat(testEmployee.getEmailAddress(), equalTo(email));assertThat(testEmployee.getFirstName(), equalTo(firstName));assertThat(testEmployee.getLastName(), equalTo(lastName));

Page 21: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 21

Test for Side Effects// have other employees been modified?employeeResults = selectStatement.executeQuery("Select * from employees where email = '[email protected]'");assertThat(employeeResults.next(), is(true));email = employeeResults.getString("email");firstName = employeeResults.getString("firstName");lastName = employeeResults.getString("lastName");assertThat("[email protected]", equalTo(email));assertThat("Bob", equalTo(firstName));assertThat("Smith", equalTo(lastName));

Page 22: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 22

Still Testing for Side Effects…employeeResults = selectStatement.executeQuery("Select * from employees where email = '[email protected]'");assertThat(employeeResults.next(), is(true));email = employeeResults.getString("email");firstName = employeeResults.getString("firstName");lastName = employeeResults.getString("lastName");assertThat("[email protected]", equalTo(email));assertThat("Ron", equalTo(firstName));assertThat("Fink", equalTo(lastName));

Page 23: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 23

Yet More Testing for Side Effects…// Were new employees created in the update process?employeeResults = selectStatement.executeQuery("select count(*) as total FROM employees");assertThat(employeeResults.next(), is(true));int totalEmployees = employeeResults.getInt("total");assertThat(totalEmployees, is(3));

Page 24: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 24

CleanupemployeeResults.close();selectStatement.close();connection.close();

Page 25: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 25

JUnit Testing

Page 26: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 26

JUnit Summary

Thorough testing requires a lot of code• Not boilerplate – corner cases and side effects must be looked for

Extensive coding required to initialize the databaseJUnit is not able to directly facilitate any of thisTakes too long and is too error prone

Page 27: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 27

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 28: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 28

DBUnit Basics

http://www.dbunit.org/JUnit extension for databasesProvides ability to• Ensure database state• Compare actual database state to expected• Filter and sort data as needed

Page 29: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 29

Simplified DBUnit Lifecycle

Constructor setUp tearDowntestX

Page 30: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 30

Simplified DBUnit Lifecycle

Constructor setUp tearDown

IDatabaseConnectionDatabaseOperationDataSet

testX

Page 31: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 31

DBUnit Best Practice

Don’t extend DBTestCase• Locks you into JUnit• DBUnit lifecycle can make it more difficult to perform certain tests• Just isn’t necessary

Simply leverage the fundamental DBUnit classes

Page 32: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 32

DBUnit Fundamental Objects

IDatabaseTester• Responsible for adding DBUnit features as composition on existing

test cases (instead of extending DBTestCase directly)Implementations• DefaultDatabaseTester – provides no database connectivity• DataSourceDatabaseTester• JdbcDatabaseTester• JndiDatabaseTester

Page 33: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 33

DBUnit Fundamental Objects

ITable• Represents a collection of tabular data• Used in assertions to compare actual database tables to expected

tablesImplementations• DefaultTable• PrimaryKeyFilteredTableWrapper• SortedTable

Page 34: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 34

DBUnit Fundamental Objects

DatabaseOperation• Represents an operation performed on the database before and

after each test• DatabaseOperation.CLEAN_INSERT• DatabaseOperation.REFRESH• DatabaseOperation.DELETE_ALL

Page 35: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 35

DBUnit Fundamental Objects

IDataSet• Collection of tables• Used to place the database into a known state• Used to compare current database state against expected state

Implementations• FlatXmlDataSet• StreamingDataSet• XmlDataSet

Page 36: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 36

FlatXmlDataSet

Reads and writes flat XML dataset documentEach XML element corresponds to a table rowEach XML element name corresponds to a table name

Page 37: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 37

Example FlatXmlDataSet

dataset>

<employees email=“[email protected]

firstName="Bob" lastName="Smith"/>

<employees email=“[email protected]

firstName="Sally" lastName="Smith"/>

<employees email="[email protected]

firstName="Ron" lastName="Fink"/>

/dataset>

Page 38: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 38

Example FlatXmlDataSet

dataset>

<employees email=“[email protected]

firstName="Bob" lastName="Smith"/>

<employees email=“[email protected]

firstName="Sally" lastName="Smith"/>

<employees email="[email protected]

firstName="Ron" lastName="Fink"/>

/dataset>

able Name

olumn Name

Page 39: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 39

dataset>

<employees email=“[email protected]

firstName="Bob" lastName="Smith"/>

<employees email=“[email protected]

firstName="Sally" lastName="Smith"/>

<employees email="[email protected]

firstName="Ron" lastName="Fink"/>

/dataset>

ow

Example FlatXmlDataSet

Page 40: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 40

DBUnit Outline

Setup the database connectionInitialize the test database with test dataExecute the code to be testedThoroughly test the results• Make sure to verify that there are no unwanted side effects

Cleanup the database connectionsSame process as with JUnit but with less code

Page 41: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 41

Housekeeping

rivate EmployeeJDBCPersistence persistence;

rivate IDatabaseTester databaseTester;

@BeforeClass

public void setUp() {

persistence = new EmployeeJDBCPersistence();

databaseTester = new

JdbcDatabaseTester("com.mysql.jdbc.Driver",uri);

}

Page 42: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 42

Setup the Initial Database State

ublic void testUpdateEmployee() throws Exception {

IDatabaseConnection connection =

databaseTester.getConnection();

InputStream dataStream =

etClass().getResourceAsStream("InitialEmployeeUpdateData.xml");

IDataSet initialDataSet = new

FlatXmlDataSet(dataStream);

DatabaseOperation.CLEAN_INSERT.execute(connection,

initialDataSet);

dataStream.close();

Page 43: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 43

Perform the Update

mployee testEmployee = new

Employee("[email protected]", "Sally", "Fink");

ersistence.updateEmployee(testEmployee);

Page 44: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 44

Test the Update

ataStream = getClass().getResourceAsStream("ExpectedEmployeeUpdateData.xml");

Table expectedEmployeeTable = new

FlatXmlDataSet(dataStream).getTable("employees");

Table actualTable =

connection.createDataSet().getTable("employees");

ssertion.assertEquals(new

SortedTable(expectedEmployeeTable), new

SortedTable(actualTable,

expectedEmployeeTable.getTableMetaData()));

onnection.close();

ataStream.close();

Page 45: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 45

DBUnit test

Page 46: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 46

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 47: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 47

Sorting

Default sorting is by primary key for tables retrieved from the databaseExpected dataset may be in a different order

Page 48: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 48

Initial Dataset

<dataset> <employees email="[email protected]" firstName="Bob" lastName="Smith"/>

<employees email="[email protected]" firstName="Sally" lastName="Smith"/>

<employees email="[email protected]" firstName="Ron" lastName="Fink"/>

</dataset>

Page 49: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 49

Expected Dataset

<dataset> <employees email="[email protected]" firstName="Bob" lastName="Smith"/>

<employees email="[email protected]" firstName="Sally" lastName="Fink"/>

<employees email="[email protected]" firstName="Ron" lastName="Fink"/>

</dataset>

Page 50: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 50

Default – Sorted by Primary Key (email)

<dataset> <employees email="[email protected]" firstName="Bob" lastName="Smith"/>

<employees email="[email protected]" firstName="Ron" lastName="Fink"/>

<employees email="[email protected]" firstName="Sally" lastName="Fink"/>

</dataset>

Page 51: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 51

SortedTable

Decorator for an existing tableSortedTable(ITable table)• Sort the decorated table by its own columns’ order

SortedTable(ITable table, ITableMetaData metaData)• Sort the decorated table by specified metadata columns’ order

Page 52: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 52

Sorting

Page 53: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 53

Filtering

Useful for removing generated primary key values from comparisons• keys generated by a sequence

Removing columns that don’t matter to the test• time stamps

Page 54: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 54

DefaultColumnFilter

IColumnFilter implementation that exposes columns matching include patterns and not matching exclude patternsexcludeColumn(String columnPattern)• Wildcard characters supported

• * - 0 or more• ? - 0 or 1

includeColumn(String columnPattern)• Wildcard characters supported

• * - 0 or more• ? - 0 or 1

Page 55: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 55

Filtering

Page 56: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 56

Agenda

Unit testing and databasesBare bones unit testingDBUnit BasicsAdvanced TopicsLimitations

Page 57: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 57

DBUnit Limitations

Not especially useful for validating queries• Query results aren’t reflected in the database• Query results can be validated through standard methods• DBUnit can still be used to populate the database with test data

Dealing with foreign keys on auto generated ids is tricky• Relationships can’t always be validated

Page 58: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 58

For More Information

http://www.dbunit.org/ Felipe Leme’s blog• http://weblogs.java.net/blog/felipeal/

Effective Unit Testing with DBUnit • http://www.onjava.com/pub/a/onjava/2004/01/21/dbunit.html

BOF-5101 Boosting Your Testing Productivity with Groovy

Page 59: Unit-Testing Database Operations with DBUnit, TS-5859, JavaOne 2008

2008 JavaOneSM Conference | java.sun.com/javaone | 59

Clark D. Richey, Jr., Principal Technologist, Mark LogicJames Morgan, Senior Software Engineer, SRATS-5859