Showing posts with label HSQLDB. Show all posts
Showing posts with label HSQLDB. Show all posts

Monday, February 9, 2009

Testing persistence layer

As a supporter of TDD I prefer to test as much as it's necessary to be sure the code works properly (but I'm not a paranoid in that). With respect to this, testing persistence layer takes a very important place in my TDD practices.

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.


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( );
}
}

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.


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() );
}
}

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':

<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<employer id="1" name="IBM" email="[email protected]" />
<employer id="2" name="Microsoft" email="[email protected]" />
</dataset>
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.


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( "[email protected]", employer.getEmail() );
}
}

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.

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.

Saturday, October 4, 2008

Integration testing: building our own test sandbox (practice)

Previous post prepared some theoretical background for building our own test sandbox. In this post we'll flesh out the theory with practice. So, let's follow startup steps and confirm each one with code snippets.

1. Configure HSQLDB data source and JNDI

import org.enhydra.jdbc.standard.StandardDataSource;
...

StandardDataSource
dataSource = new StandardDataSource();
dataSource.setDriverName( "org.hsqldb.jdbcDriver" );
dataSource.setUrl( "jdbc:hsqldb:mem:testdb" );
dataSource.setUser( "sa" );

In the snippet above I've used very useful XAPool project (to manipulate with JDBC data sources).

import javax.naming.Context;
import javax.naming.InitialContext;
import org.mortbay.jetty.plus.naming.NamingEntry;
import org.mortbay.jetty.plus.naming.Resource;
...

Context context = new InitialContext();
Context compCtx = ( Context )context.lookup( "java:comp" );
compCtx.createSubcontext( "env" );

// Configure JNDI
NamingEntry.setScope( NamingEntry.SCOPE_GLOBAL) ;

// This actually registers the resource in JNDI
new Resource( "jdbc/testdb", dataSource ).bindToENC();

2. Configure Hibernate

In fact, there is a trick here. I'm using AspectJ to intercept in run-time (weaver) all calls to Hibernate's class method Configuration.configure(). There is an aspect's code.

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.cfg.Configuration;
@Aspect
public class HibernateAspect {
@Around("execution( * org.hibernate.cfg.Configuration.configure() throws * )")
public Object reconfigure( ProceedingJoinPoint joinPoint ) throws Throwable {
Object r = ((Configuration) joinPoint.getThis()).configure( <path to configuration file> );
return r;
}
}

3. Configure Web Services for Jetty

import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.ServletHolder;
import org.apache.axis2.transport.http.AxisServlet;
import org.mortbay.jetty.servlet.Context;
...

Server server = new Server(0);

ServletHolder
axisServletholder = new ServletHolder( new AxisServlet() );
axisServletholder.setInitParameter( "axis2.xml.path", <path to axis2.xml> );
axisServletholder.setInitParameter( "axis2.repository.path", <path to WEB-INF folder> );

Context root = new Context( server, "/", Context.SESSIONS );
root.addServlet( axisServletholder, "/servlet/AxisServlet" );
root.addServlet( axisServletholder, "/services/*" );

4. Run Jetty

server.start();
int actualPort = server.getConnectors()[0].getLocalPort();

5. Export database schema (Hibernate)

import org.hibernate.tool.hbm2ddl.SchemaExport;
...

// Be sure, Hibernate uses JNDI data source name. In this test "jdbc/testdb".
//
And database dialect is set to org.hibernate.dialect.HSQLDialect.
SchemaExport schemaExport = new SchemaExport( );
schemaExport.create( true, true );

6. Prepare test dataset (DbUnit)

import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.hibernate.Session;
import java.io.FileInputStream;
...

Session session = HibernateUtil.getCurrentSession();
IDataSet dataSet = new FlatXmlDataSet(new FileInputStream(<path to test dataset XML>));
IDatabaseConnection connection = new DatabaseConnection( session.connection() );
DatabaseOperation.CLEAN_INSERT.execute( connection, dataSet );

7. Run test(s) (Java or Groovy)

Let's summarize what we have at this point:
- Jetty is up and running
- Web Services are configured and deployed at: http://localhost:<actual port>/services/*
- Database schema is created and populated with test dataset

So we're ready to pull our web service methods. But for this purpose we need web service client. The first approach is to use raw SOAP requests. Second one is to generate web service (WSDL) stubs (Java). And last but not least is to use some scripting/dynamic language (like Groovy).

My choive was Groovy because:
- it's easy to consume web services with GroovyWS library
- it's easy to write test code
- it's excellent dynamic scripting language with seamless
Java integration

Here is code sample:

import groovyx.net.ws.WSClient;

def proxy = new WSClient(
"http://localhost:<actual port>/services/TestService?wsdl", this.class.classLoader
);

def param = proxy.create( "testServiceNamespace.SomeType" )
param.someProperty =

def result = proxy.someTestMethod( param )
assert ( result = )

Looks really great and very promising. The only problem I've encountered that GroovyWS is started recently and is "green". Unfortunately, I've failed to test most web service methods with complex in/out parameters and return types (so I was reluctant to use WSDL generated stubs).

That's it. Our test sandbox is ready for use!

Integration testing: building our own test sandbox (theory)

To be honest, I aim for developing high-quality software. Unit tests, now BDD really helps with that. They allow us to test individual classes, modules, and event subsystems. But what about testing whole application with everything in place (integration testing)? Mostly, this is test teams responsibility. Before that, can developers be sure that application is not only built properly but foremost flows work properly as well? Sometimes it's not so obvious, but in general the answer is "yes".

Today I would like to share my own experience with building test sandbox for Axis 2 Web Services. Primarily, I tried to avoid any external dependencies (such as database, servers, etc.). Test sandbox should be fully reproducible and isolated.

Application architecture looks like in picture below.











So, our test sandbox should have:
- HTTP Server (and Servlet container) -> Jetty
- Database (in memory) -> HSQLDB

The flow to start up test sandbox looks like:
- Configure HSQLDB data source and JNDI
- Configure Hibernate
- Configure Web Services for Jetty
- Run Jetty
- Export database schema (Hibernate)
- Prepare test dataset (DbUnit)
- Run test(s) (Java or Groovy)

Also, using AspectJ weaver, we can reconfigure Hibernate with HSQLDB data source on the fly. It looks a little bit complicated but completely feasible. So, from theory to code ... (next post)