|
What to test against?
Now that we know what to test, we can decide what to test against. Since we are testing database code, we want our test fixture to include a database. That database can be an embedded in-memory database such as HSQLDB (in memory-only mode) or a "real" database such as MySQL or Oracle. Using an embedded database has the big advantage of being easy to set up; there is no need for everyone running the tests to have a running MySQL or Oracle instance. But if your production code runs against another database, you might not catch all database issues this way. So an integration test against a real database is also needed, but more on that later.
For most tests we need more than just the database. We need to set it up correctly before a test and after the test we need leave it in a usable state for the next test to run. Setting up the schema and filling the database with the right data before running the test are not that hard to do (a.k.a. left as an exercise for the reader ;-) ), but returning the database to a usable state after the test is a more difficult problem. I've found a number of approaches to this problem:
* The Spring Framework includes the a test framework that uses transactions to manage the state of your test fixture. If you annotate your test to be @Transactional, the SpringJUnit4ClassRunner will start a transaction before each test starts and roll back that transaction at the end of the test to return to a known state. If you are still using JUnit 3.8 you can extend the AbstractTransactionalSpringContextTests base class for the same effect. This might seem nice but in practice I've found this method to be unsatisfactory for a number of reasons:
1. By default the JPA context is not flushed until the transaction is committed or a query is executed. So unless your test includes a query, any modifications are not actually propagated to the database which can hide problems with invalid mappings and such. You could try and explicitly invoke EntityManager.flush before the end of the test, but then the tests don't represent real scenario's anymore.
2. Also, saving an entity and then retrieving it in the same session does not uncover those nasty lazy loading issues. You're probably not even hitting the database as the JPA provider will return a reference to the object that you just saved!
3. Finally, in a test you might like to first store some data in the database, then run the tests, and finally check that the right data was written out to the database. To test this properly you need three separate transactions without the first two transactions being rolled back.
* If you use an embedded in-memory database that database will be clean when you run the first test and you won't need to worry about leaving it in a good state after all the tests are run. This means you will not have to roll back any transactions and can have multiple transactions within one test. But you might have to do something special between each test. For example, when using the Spring TestContext framework you can use the @DirtiesContext annotation to reinitialize the in-memory database between tests.
* If you cannot use an in-memory database or re-initializing it after every test is too expensive, you can try and clear all the tables after every test (or before every test). For example, DbUnit can be used to delete all data from your test tables or truncate all test tables. Foreign key contraints may get in the way though, so you will want to temporarily disable referential integrity before performing these operations. |
|