SQL Zone is brought to you in partnership with:

Jens Schauder is software developer since 1997. He loves software development for the constant challenges and constantly changing environment. A great chance to learn and teach. He is also blogger, author of various articles and speaker at conferences. Jens is a DZone MVB and is not an employee of DZone and has posted 86 posts at DZone. You can read more from them at their website. View Full User Profile

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

09.07.2011
| 5509 views |
  • submit to reddit

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

    public class SuperHeroMother {
     private final Session session;
     
     private String secretIdentity = "Mr. Jones";
     private String name = "Name";
     private String weakness = "None";
     private SuperPower power = null;
     
     public SuperHeroMother(Session s) {
      session = s;
      power = new SuperPowerMother(session).instance();
     }
     
     public SuperHero instance() {
      SuperHero hero = loadInstance();
      if (hero == null) {
       hero = createInstance();
      }
      hero.power = power;
      hero.weakness = weakness;
      hero.secretIdentity = secretIdentity;
      session.save(hero);
      return hero;
     }
     
     private SuperHero loadInstance() {
      return (SuperHero) session.createCriteria(SuperHero.class)
        .add(Restrictions.eq("name", name)).uniqueResult();
     
     }
     
     private SuperHero createInstance() {
      SuperHero hero = new SuperHero();
      hero.name = name;
      return hero;
     }
     
     public SuperHeroMother name(String aName) {
      name = aName;
      return this;
     }
     
     public SuperHeroMother secretIdentity(String aSecretIdentity) {
      secretIdentity = aSecretIdentity;
      return this;
     }
     
     public SuperHeroMother power(SuperPower aPower) {
      power = aPower;
      return this;
     }
     
     public SuperHeroMother weaknes(String aWeakness) {
      weakness = aWeakness;
      return this;
     }
    }

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:

    /** create instances of type <tt>T</tt> */
    public abstract class ObjectMother<T> {
     private final Session session;
     
     public ObjectMother(Session s) {
      session = s;
     }
     
     /** returns an instance based on the configuration of this object mother */
     public T instance() {
      T t = loadInstance(session);
      if (t == null)
       t = createInstance();
      configureInstance(t);
      session.save(t);
      return t;
     }
     
     /**
      * configure the instance <tt>t</tt> according to the configuration of this
      * ObjectMother
      */
     abstract protected void configureInstance(T t);
     
     /**
      * try to load an instance based on the alternate key. Returns null if no
      * such instance exists
      */
     abstract protected T loadInstance(Session session);
     
     /**
      * create a fresh instance with the alternate key set according to the
      * configuration of this ObjectMother
      */
     abstract protected T createInstance();
    }

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

    public abstract class SingleAlternateKeyObjectMother<T, A, S extends SingleAlternateKeyObjectMother<T, A, S>>
      extends ObjectMother<T> {
     
     private final Class<T> objectType;
     private final String alternateKeyName;
     private A alternateKey;
     
     public SingleAlternateKeyObjectMother(Session s, Class<T> theObjectType,
       A defaultAlternateKey, String theAlternateKeyName) {
      super(s);
      objectType = theObjectType;
      alternateKeyName = theAlternateKeyName;
      alternateKey(defaultAlternateKey);
     }
     
     @SuppressWarnings("unchecked")
     @Override
     final protected T loadInstance(Session session) {
      return (T) session.createCriteria(objectType)
        .add(Restrictions.eq(alternateKeyName, getAlternateKey()))
        .uniqueResult();
     
     }
     
     @SuppressWarnings("unchecked")
     public S alternateKey(A theAlternateKey) {
      alternateKey = theAlternateKey;
      return (S) this;
     }
     
     public A getAlternateKey() {
      return alternateKey;
     }
    }

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

    public class SuperHeroMother extends
      SingleAlternateKeyObjectMother<SuperHero, String, SuperHeroMother> {
     
     private String secretIdentity = "Mr. Jones";
     private String weakness = "None";
     private SuperPower power;
     
     public SuperHeroMother(Session s) {
      super(s, SuperHero.class, "Name", "name");
      power = new SuperPowerMother(s).instance();
     }
     
     @Override
     protected void configureInstance(SuperHero hero) {
      hero.power = power;
      hero.weakness = weakness;
      hero.secretIdentity = secretIdentity;
     }
     
     @Override
     protected SuperHero createInstance() {
      SuperHero hero = new SuperHero();
      hero.name = getAlternateKey();
      return hero;
     }
     
     public SuperHeroMother secretIdentity(String aSecretIdentity) {
      secretIdentity = aSecretIdentity;
      return this;
     }
     
     public SuperHeroMother power(SuperPower aPower) {
      power = aPower;
      return this;
     }
     
     public SuperHeroMother weaknes(String aWeakness) {
      weakness = aWeakness;
      return this;
     }
    }

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?

 

From http://blog.schauderhaft.de/2011/03/27/testing-databases-with-junit-and-hibernate-part-3-cleaning-up-and-further-ideas/

Published at DZone with permission of Jens Schauder, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Kapil Viren Ahuja replied on Fri, 2011/09/09 - 4:26pm

An interesing approach which leaves me confused. I am thinking what you mean "Testing database". In my understanding, you write unit test cases to test you code, In your case if you are using hibernate as Data Access Layer, then you want to test your Hibernate code / setup? However, if you are going to test Save and reads, then whats the point of using hibernate which guarantees all of those functions especially of the Sessionfactory has been created successfully.

In addition, I feel if you use Spring, you can really make things much more easy and simple.

Jens Schauder replied on Wed, 2011/09/14 - 11:46am in response to: Kapil Viren Ahuja

Just using Hibernate doesn't make anything near the database fail proof.

 Among other things that might go wrong:

- your SQL, HQL or Criteria Query might not return what you want it to return.

- your Configuration might cause stuff to get persisted/updated that you don't want to get touched, by to agressive cascading. 

- Or the other way round

- Your database might not allow the values to get stored that you actually want to get stored, due to wrong constraints

- You might mess up your Session handling, causing fun things like LazyLoadingException, multiple versions of the same entity attached to the same Session. Collections attached to multiple Sessions and many more.

 

I'd be interested to learn how Spring supports these kinds of tests. Do you have any pointers?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.