Testing Databases with JUnit and Hibernate Part 3: Cleaning up and Further Ideas

This is the last part in this little series about testing of database code.
In the first part I extracted the session handling for the tests into a JUnit Rule.
In the second part I introduced ObjectMothers for easy creation of instances in the database.
In this part I’ll simplify the implementation of new ObjectMothers by extracting two superclasses and I’ll finish by sketching some further ideas for further development.

Lets have a look at the SuperHeroMother

  1. public class SuperHeroMother {
  2.  private final Session session;
  3.  
  4.  private String secretIdentity = "Mr. Jones";
  5.  private String name = "Name";
  6.  private String weakness = "None";
  7.  private SuperPower power = null;
  8.  
  9.  public SuperHeroMother(Session s) {
  10.   session = s;
  11.   power = new SuperPowerMother(session).instance();
  12.  }
  13.  
  14.  public SuperHero instance() {
  15.   SuperHero hero = loadInstance();
  16.   if (hero == null) {
  17.    hero = createInstance();
  18.   }
  19.   hero.power = power;
  20.   hero.weakness = weakness;
  21.   hero.secretIdentity = secretIdentity;
  22.   session.save(hero);
  23.   return hero;
  24.  }
  25.  
  26.  private SuperHero loadInstance() {
  27.   return (SuperHero) session.createCriteria(SuperHero.class)
  28.     .add(Restrictions.eq("name", name)).uniqueResult();
  29.  
  30.  }
  31.  
  32.  private SuperHero createInstance() {
  33.   SuperHero hero = new SuperHero();
  34.   hero.name = name;
  35.   return hero;
  36.  }
  37.  
  38.  public SuperHeroMother name(String aName) {
  39.   name = aName;
  40.   return this;
  41.  }
  42.  
  43.  public SuperHeroMother secretIdentity(String aSecretIdentity) {
  44.   secretIdentity = aSecretIdentity;
  45.   return this;
  46.  }
  47.  
  48.  public SuperHeroMother power(SuperPower aPower) {
  49.   power = aPower;
  50.   return this;
  51.  }
  52.  
  53.  public SuperHeroMother weaknes(String aWeakness) {
  54.   weakness = aWeakness;
  55.   return this;
  56.  }
  57. }

There is still quite some code wich will get repeated over and over again for each ObjectMother. So lets extract some of the commonalities into a superclass ObjectMother:

  1. /** create instances of type <tt>T</tt> */
  2. public abstract class ObjectMother<T> {
  3.  private final Session session;
  4.  
  5.  public ObjectMother(Session s) {
  6.   session = s;
  7.  }
  8.  
  9.  /** returns an instance based on the configuration of this object mother */
  10.  public T instance() {
  11.   T t = loadInstance(session);
  12.   if (t == null)
  13.    t = createInstance();
  14.   configureInstance(t);
  15.   session.save(t);
  16.   return t;
  17.  }
  18.  
  19.  /**
  20.   * configure the instance <tt>t</tt> according to the configuration of this
  21.   * ObjectMother
  22.   */
  23.  abstract protected void configureInstance(T t);
  24.  
  25.  /**
  26.   * try to load an instance based on the alternate key. Returns null if no
  27.   * such instance exists
  28.   */
  29.  abstract protected T loadInstance(Session session);
  30.  
  31.  /**
  32.   * create a fresh instance with the alternate key set according to the
  33.   * configuration of this ObjectMother
  34.   */
  35.  abstract protected T createInstance();
  36. }

ObjectMother uses the Template Method Pattern in order to coordinate the provisioning of intances: try to load a matching instance from the database, if this doesn’t work, create it from scratch, configure all attributes of the instance, store it in the database and return it. The loading, creating and configuring needs to get implemented in subclasses.

For the three entities used in this series we can extract even more code from the various ObjectMothers because loading the instance from the database works always in exactly the same way. But this is only the case because we have an alternate key based on a single attribute. This will be often the case but not always, so I don’t want to tie this into the ObjectMother. Instead I’ll create another super class for this special kind of ObjectMother: SingleAlternateKeyObjectMother

  1. public abstract class SingleAlternateKeyObjectMother<T, A, S extends SingleAlternateKeyObjectMother<T, A, S>>
  2.   extends ObjectMother<T> {
  3.  
  4.  private final Class<T> objectType;
  5.  private final String alternateKeyName;
  6.  private A alternateKey;
  7.  
  8.  public SingleAlternateKeyObjectMother(Session s, Class<T> theObjectType,
  9.    A defaultAlternateKey, String theAlternateKeyName) {
  10.   super(s);
  11.   objectType = theObjectType;
  12.   alternateKeyName = theAlternateKeyName;
  13.   alternateKey(defaultAlternateKey);
  14.  }
  15.  
  16.  @SuppressWarnings("unchecked")
  17.  @Override
  18.  final protected T loadInstance(Session session) {
  19.   return (T) session.createCriteria(objectType)
  20.     .add(Restrictions.eq(alternateKeyName, getAlternateKey()))
  21.     .uniqueResult();
  22.  
  23.  }
  24.  
  25.  @SuppressWarnings("unchecked")
  26.  public S alternateKey(A theAlternateKey) {
  27.   alternateKey = theAlternateKey;
  28.   return (S) this;
  29.  }
  30.  
  31.  public A getAlternateKey() {
  32.   return alternateKey;
  33.  }
  34. }

With this a SuperHeroMother contains only a couple of extremly simple methods with hardly any code duplication left:

  1. public class SuperHeroMother extends
  2.   SingleAlternateKeyObjectMother<SuperHero, String, SuperHeroMother> {
  3.  
  4.  private String secretIdentity = "Mr. Jones";
  5.  private String weakness = "None";
  6.  private SuperPower power;
  7.  
  8.  public SuperHeroMother(Session s) {
  9.   super(s, SuperHero.class, "Name", "name");
  10.   power = new SuperPowerMother(s).instance();
  11.  }
  12.  
  13.  @Override
  14.  protected void configureInstance(SuperHero hero) {
  15.   hero.power = power;
  16.   hero.weakness = weakness;
  17.   hero.secretIdentity = secretIdentity;
  18.  }
  19.  
  20.  @Override
  21.  protected SuperHero createInstance() {
  22.   SuperHero hero = new SuperHero();
  23.   hero.name = getAlternateKey();
  24.   return hero;
  25.  }
  26.  
  27.  public SuperHeroMother secretIdentity(String aSecretIdentity) {
  28.   secretIdentity = aSecretIdentity;
  29.   return this;
  30.  }
  31.  
  32.  public SuperHeroMother power(SuperPower aPower) {
  33.   power = aPower;
  34.   return this;
  35.  }
  36.  
  37.  public SuperHeroMother weaknes(String aWeakness) {
  38.   weakness = aWeakness;
  39.   return this;
  40.  }
  41. }

Note that the three last methods are only necessary when you want to control these three attributes in your tests. Also these methods actually do suffer from code duplication, but I don’t see a way to abstract over this duplication without using lots of reflection which I don’t consider worthwhile in this case.

With this infrastructure setup there shouldn’t be a reason to leave any SQL statement untested. There are some special cases though.

As mentioned in the first part of this series you should use an in memory database for these tests. It is by orders of magnitudes faster then a normal persistent database. With an in memory database (HSQLDB) we execute about 400 tests in about 5 minutes in a current project. I consider this slow as mud for unit test standards, but compare it to 90 minutes for the same tests against an Oracle database. But sometimes you might have SQL statements that are specific for a database. For example we use analytic functions which are extremely powerfull, but not supported by HSQLDB and as far as I know by no other free in memory database. In order to test these statements and still have a fast running test suite, we further extended our session factory rule. It checks if a the test method or test class is annotated with a special annotation (@OracleTest) and also checks the SqlDialect of the HibernateConfiguration. If the annotation is present, but the SqlDialect is not an Oracle dialect it does not execute the test. Our continuos integration system (Jenkins) has two jobs for the test, one configured with an oracle database and one with a HSQLDB in memory database. The later gives a fast feedback for each check in into version control and the second runs the slow, but more complete tests.

While the stuff presented here is in my opinion extremely helpfull for unit tests of database access there are other things that need testing in the context of databases. You should especially consider tests for your ddl statements and performance tests. For tests of ddl statements I provided ideas in a former article. For performance tests the approach of ObjectMothers is at least in its current form mostly useless, because creating the large amounts of data would be way to slow. So special techniques are needed here. Maybe somebody can provide helpfull links in the comments?

Share:
  • DZone
  • Digg
  • del.icio.us
  • Reddit
  • Facebook
  • Twitter