testing without the embedded container

John Sanda jsanda at redhat.com
Mon Nov 29 20:33:37 UTC 2010


A while back when I was testing JPQL queries that I had updated to 
account for resource types being deleted, I got tired off using the 
embedded container. The start up time in particular is what wore my 
patience thin. It dawned on me as I was running my tests in core/domain, 
which is where we define all of our JPQL queries, that there are only 
two things for which we are using the embedded container in core/domain 
tests - 1) entity manager and 2) transactions. We can get both of these 
things without the embedded container. A little over a month I created a 
branch in which I refactored all of the tests in core/domain such that 
there is no longer any dependency on the embedded container. 
Specifically, tests no longer inherit from AbstractEJB3Test.

Tests are instead relying on unitils (http://www.unitils.org). Unitils 
provides a lot of useful, interesting features. There are three things 
for which I was using it.

1) A persistence service. Unitils can inject an EntityManager, 
EntityManagerFactory, and DataSource into tests using standard JEE 
annotations.
2) A transaction service. Each test method by default is wrapped in a 
transaction.
3) Integration with DbUnit. Unitils executes a dbunit data set 
immediately before each test method is executed, ensuring that the 
database is in a known, consistent state before the test begins.

There are a number of upsides to this change. The overall build time for 
core/domain is a good bit faster. More importantly though, executing a 
single test class is substantially faster since there is no longer the 
overhead of bootstrapping the embedded container.

I simplified the control flow in a large number of tests. There is 
longer any need for the try/finally/rollback pattern that we commonly 
used. In fact, you will notice that the test code has been taken out of 
those try/finally blocks altogether. This *does* mean that tests are 
committing transactions, and that's ok since each test resets the 
database (or more precisely, relevant parts of the database) before it 
runs. Committing transactions has the added benefit of being able to 
inspect the state of the database after a test failure. I am sure most 
of us at some point have spent time debugging a test failure and 
resorted to temporarily changing the rollback call to a commit in the 
test method so that we could inspect the state of the database. That is 
not only unnecessarily time-consuming, it is also very error-prone.

Using dbunit can and does simplify the set up logic for tests. It 
provides a way to do test set up independent of the code under test. It 
also makes for good documentation and makes tests more readable.

It would remiss of me not to point out some of the downsides of using 
unitils. Unitils is somewhat of a glue framework in that it ties 
together nicely some other frameworks for use in JUnit and TestNG tests. 
Under the covers it uses spring, dbunit, hibernate, and several other 
libraries. This can (and did for me) lead to some problems with 
dependency management. Granted we are only talking about test scope, but 
I still had to spend time resolving a few dependency conflicts.

Defining the dbunit data sets in xml might be considered another 
downside. It limits the use of some refactoring tools. If I change part 
of an entity mapping in the Java source file, I will have to 
independently make any changes necessary to the data set file. 
Everything in the xml data set is defined statically. If I need to 
create a number of rows in a table that let's say differ in only ids, I 
have to write out each one in the data set whereas a simple, compact 
loop to generate those rows would likely be more preferable.  
Fortunately, both dbunit and unitils are pretty flexible. We are not 
limited to defining data sets in xml, although that is likely to be the 
easiest and fastest approach most of the time.

The other issue with dbunit is that it does not manage foreign keys. For 
an entity like ResourceType, care has to be taken to ensure that things 
are deleted in the correct order; otherwise, you will run into FK 
constraint violations. I have found though that once the data set is set 
up, it is pretty easy to manage with respect to FKs.

I want to emphasize that my goal was not to simply replace the embedded 
container with unitils. Rather, the intention was to make tests faster, 
more concise, more reliabe, and easier to maintain. Unitils is just one 
way to achieve this, but I do think it is a very good way to achieve it. 
I did not integrate unitils into any of the server/jar tests since we 
rely heavily on the embedded container there for accessing our SLSBs.

I did this work in the unitils branch, and it can all be found in a 
single commit - 0e666c041b3047c5a8919325615569c0d95a06d7. Please look at 
some of the tests in the branch. I would like to get some feedback and 
consider eventually integrating this upstream into master.

- John


More information about the rhq-devel mailing list