By "testing persistence layer" I basically mean two things:
- testing that ORM mapping (Hibernate) is valid
- testing that application works well with rich test data sets
Using persistence layer tests allows to verify corner cases as well as application behavior in case of different database faults. Even more, development with testing in mind allows to design low coupled and well layered application architecture.
To start with, let's reuse two classes Employer and Employee from previous post and develop simple test cases for them. Thanks to Hibernate, we're completely decoupled from underlying database so we can create any suitable testing configuration. And thanks to HSQLDB project we're able to easily create in-memory database and relegate standalone database server deployment. For sure, we'll create test cases based on JUnit framework.
First, let's create abstract test case encapsulating all configuration in it. Basically, we have to configure Hibernate, add annotated classes, and create session factory.
Then, let's develop a simple test case for persisting Employer and Employee classes. There's one important note here. Those test methods should be executed within transaction boundary (for example, with help of AspectJ). Otherwise, the HibernateException comes up.
import org.hibernate.Session;
import org.hibernate.dialect.HSQLDialect;
import org.hsqldb.jdbcDriver;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Environment;
public abstract class AbstractPersistentTestCase {
private AnnotationConfiguration configuration;
private SessionFactory sessionFactory;
@Before
protected void setUp() throws Exception {
configuration = new AnnotationConfiguration();
configuration.setProperty( Environment.DIALECT,
HSQLDialect.class.getName() );
configuration.setProperty( Environment.DRIVER,
jdbcDriver.class.getName() );
configuration.setProperty( Environment.URL,
"jdbc:hsqldb:mem:testdb" );
configuration.setProperty( Environment.CURRENT_SESSION_CONTEXT_CLASS,
"org.hibernate.context.ThreadLocalSessionContext" );
configuration.setProperty( Environment.HBM2DDL_AUTO,
"create-drop" );
configuration.setProperty( Environment.STATEMENT_BATCH_SIZE,
"0" );
configuration.setProperty( Environment.SHOW_SQL,
"false" );
configuration.setProperty( Environment.FORMAT_SQL,
"true" );
configuration.addAnnotatedClass( Employee.class )
.addAnnotatedClass( Employer.class );
sessionFactory = configuration.buildSessionFactory();
}
@After
public void tearDown() {
SchemaExport schemaExport = new SchemaExport( configuration );
schemaExport.drop( true, true );
}
protected Session getSession() {
return sessionFactory.getCurrentSession( );
}
}
Here I covered very simple scenario. Just to give the idea. Next step will be preparing test data sets with help of DbUnit. It allows to prepare data in XML format (among others) and upload this file into database. Let's prepare quite simple XML file 'dataset.xml':
public class PersistentTestCase extends AbstractPersistentTestCase {
@Test
public void testSaveEmployer() {
Employer employer = new Employer();
getSession().save( employer );
Assert.assertNotNull( employer.getId() );
}
@Test
public void testSaveEmployee() {
Employer employer = new Employer();
getSession().save( employer );
Employee employee = new Employee();
employee.setEmployer( employer );
getSession().save( employee );
Assert.assertNotNull( employee.getId() );
}
}
And that's it! DbUnit will automatically map XML elements to tables and XML attributes to columns! Awesome! Let's develop the test case using DbUnit and newly created data set.
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<employer id="1" name="IBM" email="ibm@ibm.com" />
<employer id="2" name="Microsoft" email="microsoft@microsoft.com" />
</dataset>
This scenario is very simple as well. But it's just a foundation ... Developing comprehensive datasets and testing application business logic against them is great step in achieving high product quality. Those techniques allow to fully cover very complex flows with tests and detect errors early.
public class DatasetTestCase extends AbstractPersistentTestCase {
@Before
protected void setUp() throws Exception {
super.setUp();
uploadTestDataset();
}
private void uploadTestDataset() throws Exception
{
final Session session = getSession();
Transaction t = session.beginTransaction( );
try {
IDataSet dataSet = new FlatXmlDataSet(
new FileInputStream(
new File(
getClass().getResource( "/dataset.xml" ).toURI()
)
)
);
IDatabaseConnection connection =
new DatabaseConnection( session.connection() );
DatabaseOperation.CLEAN_INSERT.execute( connection,
dataSet );
t.commit( );
} finally {
if ( t.isActive( ) ) {
t.rollback( );
}
}
}
@Test
public void testLoadEmployer() {
Employer employer = ( Employer )getSession().load(
Employer.class, new Integer( 1 ) );
Assert.assertEquals( "IBM", employer.getName() );
Assert.assertEquals( "ibm@ibm.com", employer.getEmail() );
}
}
The only problem with that is ... maintenance. It's particularly true for projects in active development. Changes in business logic lead to test failures (in most cases). Supporting huge tests code base can me nightmare. So ... I'm always looking for balanced solution.
No comments:
Post a Comment