alt="Unit-Testing Hibernate With HSQLDB">





April 2005


Discussion






The Motivation


I've used lots of methods to transform data between databases and object code.
From hand-coded SQL to JDO to EJB. I've never found a method I liked particularly
well. This distaste has become especially acute since adopting test-driven
development (TDD) as a guiding philosophy.


Unit-testing should have as few barriers as possible. For relational databases
those barriers range from external dependencies (is the database running?)
to speed to keeping the relational schema synchronized with your object model.
For these reasons it is vital to keep database access code away from the core
object model and to test as much as possible without touching a real database.


This has often led me to one of two patterns. The first is externalizing all
data access to domain objects and their relationships to separate classes or
interfaces. These are typically data store objects that can retrieve, edit,
delete and add domain entities. This is the easiest to mock-out for unit-testing,
but tends to leave your domain model objects as data-only objects with little
or no related behavior. Ideally access to child records would be directly from
the parent object rather than handing the parent object to some third-party
class to determine the children.


The other method has been to have the domain objects have access to an interface
into the data-mapping layer a la Martin Fowlers Data Mapper pattern.
This has the advantage of pushing object relationships inside the domain model
where the object-relational interface can be expressed once. Classes that use
the domain model are unaware of the persistence mechanism because it is internalized
into the domain model itself. This keeps your code focused on the business
problem you are trying to solve and less about the object-relational mapping
mechanism.


My current project involves crunching a number of baseball statistics and
running simulations with the data. Since the data was already in a relational
database it was a chance for me to explore the Hibernate object-relational
mapping system. I have been very impressed with Hibernate, but I ran into the
problem was trying to insert a layer of indirection while using Hibernate as
my data mapper for unit-testing. The extra layer was so flimsy that it felt
embarrassing to write it. The real deployed version was simply a pass-through
to a Hibernate-specific implementation. Even worse, the mock versions had more
complexity in them than the real "production" version simply because
they didn't have some of the basic object storage and mapping that came with
Hibernate.


I also had enough complex Hibernate query usage that I wanted to unit-test
this significat portion of the application. However, testing against a live database
is a bad idea, because it almost invariably introduces a maintenance nightmare.
In addition, since tests are best when they are independent from each other,
using the same obvious primary keys in test fixture data means you have to
create code to clean the database before each test case, which is a real problem
when lots of relationships are involved


By using HSQLDB and Hibernate's powerful schema-generation tool I was able
to unit-test the mapping layer of the application and find numerous bugs in
my object queries I would not have found as easily by manual testing. With
the techniques outlines below I was able unit-test my entire application during
development with no compromises in test coverage.


Setting up HSQLDB


I used version 1.7.3.0 of HSQLDB. To use an in-memory version of the database
you need to invoke the static loader for the org.hsqldb.jdbcDriver. Then when
you get a JDBC connection you use JDBC url such as jdbc:hsqldb:mem:yourdb where
'yourdb' is the name of the in-memory database you want to use.


Since I'm using Hibernate (3.0 beta 4), I hardly ever need to touch real-live
JDBC objects. Instead I can let Hibernate do the heavy lifting for me--including
automatically creating the database schema from my Hibernate mapping files.
Since Hibernate creates its own connection pool it will automatically load
the HSQLDB JDBC driver based on the configuration code lives in a class called
TestSchema. Below is the static initializer for the class.



public class TestSchema {

static {
Configuration config = new Configuration().
setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect").
setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver").
setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:baseball").
setProperty("hibernate.connection.username", "sa").
setProperty("hibernate.connection.password", "").
setProperty("hibernate.connection.pool_size", "1").
setProperty("hibernate.connection.autocommit", "true").
setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider").
setProperty("hibernate.hbm2ddl.auto", "create-drop").
setProperty("hibernate.show_sql", "true").
addClass(Player.class).
addClass(BattingStint.class).
addClass(FieldingStint.class).
addClass(PitchingStint.class);

HibernateUtil.setSessionFactory(config.buildSessionFactory());
}

Hibernate provides a number of different ways to configure the framework,
including programmatic configuration. The code above sets up the connection
pool. Note that the user name 'sa' is required to use HSQLDB's in-memory
database. Also be sure to specify a blank as the password. To enable Hibernate's
automatic schema generation set the hibernate.hbm2ddl.auto property to 'create-drop'.


Testing In Practice


My project is crunching a bunch of baseball statistics so I add the four classes
that I'm mapping ( Player, PitchingStint, BattingStint and FieldingStint).
Finally I create a Hibernate SessionFactory and insert it into the HibernateUtil
class which simply provides a single access method for my entire application
for Hibernate sessions. The code for the HibernateUtil is below:



import org.hibernate.*;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

private static SessionFactory factory;

public static synchronized Session getSession() {
if (factory == null) {
factory = new Configuration().configure().buildSessionFactory();
}
return factory.openSession();
}

public static void setSessionFactory(SessionFactory factory) {
HibernateUtil.factory = factory;
}
}

Since all of my code (production code as well as unit-tests) get their Hibernate
sessions from the HibernateUtil I can configure it in one place. For unit-tests
the first bit of code to access the TestSchema class will invoke the static
initializer which will setup Hibernate and inject the test SessionFactory into
the HibernateUtil. For production code the SessionFactory will be initialized
lazily using the standard hibernate.cfg.xml configuration mechanism.


So what does this look like in the unit-tests? Below is a snippet of a test
that checks the the logic for determining what positions a player is eligible
to play at for a fantasy baseball league:



public void testGetEligiblePositions() throws Exception {
Player player = new Player("playerId");
TestSchema.addPlayer(player);

FieldingStint stint1 = new FieldingStint("playerId", 2004, "SEA", Position.CATCHER);
stint1.setGames(20);
TestSchema.addFieldingStint(stint1);

Set positions = player.getEligiblePositions(2004);
assertEquals(1, positions.size());
assertTrue(positions.contains(Position.CATCHER));
}

I first create a new Player instance and add it to the TestSchema via the
addPlayer() method. This step must occur first because the FieldingStint
class has a foreign-key relationship to the Player class. If I didn't add this
instance first I would get a foreign-key constraint violation when I try to
add the FieldingStint. Once the test-fixture is in place I can test the getEligiblePositions()
method to see that it retrieves the correct data. Below is the code for the
addPlayer() method in the TestSchema. You will notice that Hibernate is used
instead of bare-metal JDBC code:


}



public static void addPlayer(Player player) {
if (player.getPlayerId() == null) {
throw new IllegalArgumentException("No primary key specified");
}

Session session = HibernateUtil.getSession();
Transaction transaction = session.beginTransaction();
try {
session.save(player, player.getPlayerId());
transaction.commit();
}
finally {
session.close();
}
}

One of the most important things in unit-testing is to keep your test-cases
isolated. Since this method still involves a database, you need a way to clean
your database prior to each test case. I have four tables in my schema so I
wrote a reset() method on the TestSchema that removes all rows from the tables
using JDBC. Note because HSQLDB knows about foreign keys, the order in which
the tables are deleted is important. Here is the code:



public static void reset() throws SchemaException {
Session session = HibernateUtil.getSession();
try {
Connection connection = session.connection();
try {
Statement statement = connection.createStatement();
try {
statement.executeUpdate("delete from Batting");
statement.executeUpdate("delete from Fielding");
statement.executeUpdate("delete from Pitching");
statement.executeUpdate("delete from Player");
connection.commit();
}
finally {
statement.close();
}
}
catch (HibernateException e) {
connection.rollback();
throw new SchemaException(e);
}
catch (SQLException e) {
connection.rollback();
throw new SchemaException(e);
}
}
catch (SQLException e) {
throw new SchemaException(e);
}
finally {
session.close();
}
}

When bulk deletes become finalized in Hibernate 3.0 we should able to remove
this last bit of direct JDBC from our application. Until then we have to get
a Connection and issue direct SQL to the database.


Be sure not to close your Connection, closing the Session is sufficient for
resource cleanup. Out of habits developed from writing lots of hand-crafted
JDBC code, the first version closed the JDBC Connection. Since I configured
Hibernate to create a connection pool with only one Connection I completely
torpedoed any tests after the first one.Be sure to watch out for this!


Since you can never be sure what state the database may be in when your test
class is running (imagine running all of your test cases), you should include
database cleanup in your setUp() methods like so:



public void setUp() throws Exception {
TestSchema.reset();
}

Conclusion


Being able to test against a real-live RDBMS without all of the hassles of
trying to run tests against your deployed database is essential, even when
working with sophisticated O/R mappers like Hibernate. The example I showed
here is not exclusive to Hibernate and could probably be made to work with
JDO or TopLink, though Hibernate makes this kind of testing particularly easy
since it has a built-in schema generation tool. With a setup like the one described
above you don't ever have to leave the comfort of your IDE and still have extensive
test coverage over your code.


Posted by 아름프로
BLOG main image

카테고리

분류 전체보기 (539)
이야기방 (19)
토론/정보/사설 (16)
IBM Rational (9)
U-IT (0)
SOA/WS/ebXML (110)
개발방법론/모델링 (122)
J2SE (34)
J2EE (60)
DataBase (39)
Open Projects (30)
BP/표준화 (50)
Apache Projects (15)
Web/보안/OS (22)
Tools (7)
AJAX/WEB2.0 (1)
Linux/Unix (1)
영어 (0)
비공개방 (0)

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

달력

«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

글 보관함

Total :
Today : Yesterday :