testing without the embedded container
by John Sanda
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
13 years, 5 months
the wonder of javadoc
by John Mazzitelli
Just wanted to give a shout out to Joseph M and to document a really
good example why javadoc is so important - even as little as a single
line comment can make a difference.
So, I'm trying to fix the Problem Groups view. There is a comment in the
view code that someone wrote saying "there is no underlying support for
this criteria." - hence why its broke. Damn! I say. Now I have to go
hunt around to find out what is missing in our criteria implementation
that this TODO mentions, write all new code (I need a criteria that
selects all groups, but only if the group has 1 or more members in the
DOWN state - how exactly DO I get a filter criteria to select where
there is "more than N" I ask??), and spend time testing. I'm thinking
easily a couple hours this is gonna waste.
Before I went down that rabbit hole, I stopped and said, "let me make
SURE this really isn't supported before I go wasting time and write new
code - who knows, maybe since the time that comment was written, the
underlying support magically appeared :) Hope springs eternal, I say.
I find this addFilterDownMemberCount in the criteria - but, I discount
it because, well, that can't be it because I want to specify "greater
than 1" - all of our filters that I'm used to using must match exactly
to the filter value - so setting that filter to 1 wouldn't make sense -
I don't want all groups with a down member count of just 1, I want
groups with ONE OR MORE downed members.
Then I look at the javadoc - which is just a very simple one-liner:
http://git.fedorahosted.org/git/?p=rhq/rhq.git;a=blob;f=modules/core/doma...
/**
* Only returns groups with at least this many downed implicit
resource members
*/
Bingo! ..."returns groups with at least this many downed members"...
That IS what I need! This IS supported in the underlying implementation!
It magically appeared afterall. And there was much rejoicing. <minstrel
music plays in background>
That javadoc - that one simple, single-line,
probably-took-joe-10-seconds-to-write comment - just saved me hours of time.
:) I'm gonna be a little more vigilant in writing javadoc - I admit that
I myself have been slacking in that regard lately.
13 years, 5 months