Okay, I found why my DBUnit tests were not working:  I was overriding setUp() but not calling its parent version.

The fix was simply to invoke super.setUp() in my own setUp():

public void setUp() {
  try {
    Class.forName(JDBC_DRIVER).newInstance();
    super.setUp();

  …

Note that the order of these two instructions is important, or
DatabaseTestCase will be unable to locate your driver. 
Seems obvious, but I didn’t get it right the first time.

Now, all this makes me angry for a lot of reasons.

First of all, this is the kind of design flaws that has been around since the
C++ days.  I
weblogged about it
a while ago:

This kind of pattern
is similar to seeing methods invoke their super counterpart, a definite code
smell in my book (if you override the said method and forget to call super,
everything breaks).

I’ll restate my point:  whenever you feel the need to call super inside
a method that is not a constructor, it’s a code smell.  If on top of that, this method can be
overridden by subclasses, you absolutely need to get rid of that constraint
because I guarantee you that someone (a user or even yourself) will break that
contract.

How do you solve this problem?  With a technique called "hooks".

In this particular example, DatabaseTestCase.setUp() performs some very important
initialization logic.  If this code is not run, then DBUnit breaks. 
As simple as that.  The problem is that subclasses are very likely to
override setUp(), since it’s the recommended and documented way to set up your
tests in JUnit.

When you face such a situation, you should consider moving the vital code in
a private method that cannot be subclassed, and then have this method invoke one
or several
"hooks".  The hook would be, in that case "onSetUp()".  This way,
subclasses can be notified when the setUp() is happening but they won’t override
the important initialization that’s happening in it.

Admittedly, this technique has limits when the hierarchy of subclasses
deepens, and there is no easy way to achieve that, so DBUnit is not
completely to blame.

The real culprit is JUnit which was designed without realizing that
subclasses of TestCase can be either more specialized parent test classes or
real tests, and
that subclassing rules should be different depending on which class you are
implementing.

The more I work with JUnit, the more angry
<blam> I
<blam> get.