Spring Test y DbUnit

Hace ya un tiempo, escribí un artículo acerca de como Spring ampliaba los test de JUnit y cómo estos ayudaban haciendo rollback de la transacción después de cada test. Acabo de leer un artículo que soluciona uno de los problemas que surgen cuando se realiza un test de un método que llama a la base de datos. Básicamente, el problema que surge es que cuando uno ejecuta un test que llama a una base de datos el test puede fallar porque la base de datos nunca está en el mismo estado.

 

El artículo muestra una manera de evitar esto, para eso utiliza DbUnit.

 

El primer paso que realiza el autor es pasar los datos de las tablas que deseemos a un fichero xml:

 

import java.io.FileOutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.database.IDatabaseConnection;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.xml.FlatXmlWriter;

public class TestDBUnit
{
public static void main(String[] args)
throws Exception
{
//Connect to the database
DriverManager.registerDriver(new net.sourceforge.jtds.jdbc.Driver());
Connection conn = DriverManager.getConnection(“URL_TO_CONNECT”, “username”, “password”);

IDatabaseConnection connection = new DatabaseConnection( conn );

QueryDataSet partialDataSet = new QueryDataSet(connection);
//Specify the SQL to run to retrieve the data
partialDataSet.addTable(“person”, ” SELECT * FROM person WHERE name=’Test name’ “);
partialDataSet.addTable(“address”, ” SELECT * FROM address WHERE addressid=1 “);

//Specify the location of the flat file(XML)
FlatXmlWriter datasetWriter = new FlatXmlWriter(new FileOutputStream(“temp.xml”));

//Export the data
datasetWriter.write( partialDataSet );
}
}

 

Imaginemos que la tabla Person tiene esta fila:

 

PersonId = 1

Name = TestName

DOB = 5/7/2007

 

y la tabla Addres:

 

AddressId = 1

Address = 23 Some St

 

El susodicho código generará este fichero en el que el usuario puede editar las filas e incluso añadir nuevas si quiere:

 

< ?xml version=’1.0′ encoding=’UTF-8′?>
< dataset >

< address addressid=”1″ address=”23 Some street” / >
< /dataset >
Podemos importar los datos del fichero al test utilizandoDbUnit, añadiendo este código a los métodos setUp() y tearDown() que se ejecutan antes y después de cada test respectivamente.public class DBUnitTests extends TestCase
{
public void setUp()
{
System.out.println(“In setup”);
Connection conn = null;
try
{
DriverManager.registerDriver(new net.sourceforge.jtds.jdbc.Driver());
conn = DriverManager.getConnection(“URL”, “username”, “password”);
IDatabaseConnection connection = new DatabaseConnection( conn );
DatabaseOperation.INSERT.execute(connection, new FlatXmlDataSet(new FileInputStream(“temp.xml”)));
conn.close();
}
catch(Exception exc)
{
exc.printStackTrace();
}
}
public void tearDown()
{
System.out.println(“In tearDown”);
Connection conn = null;
try
{
DriverManager.registerDriver(new net.sourceforge.jtds.jdbc.Driver());
conn = DriverManager.getConnection(“URL”, “username”, “password”);
IDatabaseConnection connection = new DatabaseConnection( conn );
DatabaseOperation.DELETE.execute(connection, new FlatXmlDataSet(new FileInputStream(“temp.xml”)));
conn.close();
}
catch(Exception exc)
{
exc.printStackTrace();
}
}
public void testDBUnitImport1()
{
System.out.println(“In testDBUnitImport1″);
//Run the test
}
public void testDBUnitImport2()
{
System.out.println(“In testDBUnitImport2″);
//Run the test
}
}

De tal manera que se importarán los datos antes de cada test en la base de datos y se eliminarán después del mismo.

Ahora le vamos a poner el broche de oro, ayudándonos de los test de Spring y simplificando las cosas. Habrá que hacer varias cosas: lo primero es que en vez de extender la clase TestCase, extenderemos AbstractTransactionalDataSourceSpringContextTests que es la clase para testear métodos que llaman a orígenes de datos. Lo segundo es reescribir onSetUpInTransaction() y onTearDownInTransaction() que se ejecutan al abrir y cerrar una transacción respectivamente añadiendo el código que se encuentra en los métodos setUp() y tearDown() del ejemplo anterior, pero utilizando la template de Jdbc que proporciona Spring. Lo tercero es inyectar nuestra lógica de negocio (como siempre que se utiliza Spring).

public class AbstractTransactionalHibernateTests extends AbstractTransactionalDataSourceSpringContextTests
{
private static String TEST_DATA_FILE = “dbunit-test-data.xml”;

private TestDao testDao = null;

protected String[] getConfigLocations(){

return new String[] {“classpath: springConfig.xml”};
}

protected void onSetUpInTransaction()
throws Exception
{
logger.info(“*** Inserting test data ***”);
//Use spring to get the datasource
DataSource ds = this.jdbcTemplate.getDataSource();
Connection conn = ds.getConnection();
try
{
IDatabaseConnection connection = new DatabaseConnection( conn );
DatabaseOperation.INSERT.execute(connection, new FlatXmlDataSet(new FileInputStream(TEST_DATA_FILE)));
}
finally
{
DataSourceUtils.releaseConnection(conn, ds);
logger.info(“*** Finished inserting test data ***”);
}
}

protected void onTearDownInTransaction()
throws Exception
{
//Commit or rollback the transaction
endTransaction();

//Delete the data
DataSource ds = this.jdbcTemplate.getDataSource();
Connection conn = ds.getConnection();
try
{
IDatabaseConnection connection = new DatabaseConnection( conn );
DatabaseOperation.DELETE.execute(connection, new FlatXmlDataSet(new FileInputStream(TEST_DATA_FILE)));
}
finally
{
DataSourceUtils.releaseConnection(conn, ds);
logger.info(“*** Finished removing test data ***”);
}
}

public void testGetUsingId()
{
//do something
}

//Called by spring to inject the testDao
public void setTestDao(TestDao testDao)
{
this.testDao = testDao;
}
}

Como antes, se cargarán los datos antes de cada test y se borrarán después del mismo asegurando el estado de la base de datos para que nuestros test no fallen. Cuando utiliceis Spring acordaros de proporcionar un método set para inyectar el objeto que queraís, para los que esteís familiarizados con Spring este código os será muy familiar.

Por último, sólo queda añadir las siguientes líneas al archivo de configuración de Spring para que la inyección se haga correctamente y c’est fini:

< ?xml version=”1.0″ encoding=”UTF-8″? >
< !DOCTYPE beans PUBLIC “-//SPRING//DTD BEAN//EN”
“http://www.springframework.org/dtd/spring-beans.dtd” >

< beans>
< bean id=”testDao” class=”test.TestDaoImpl” parent=”abstractHibernateDao” / >
< /beans
>

Os dejo un link al artículo original para los que queraís profundizar en el tema:

http://www.theserverside.com/tt/articles/article.tss?l=ManageTestDataSpringandDBunit

 

 

3 Comentarios

  • 1. Alex  |  septiembre 26th, 2007 at 1:30 pm

    Veo que has estado trabajando en lo mismo que yo esta semana.

    Ten en cuenta que si usas AbstractTransactionalDataSourceSpringContextTests
    no hace falta que uses tearDownOnTransaction, puesto que los datos que insertes en el setUp se borraran al finalizar el test igualmente.

    En la version que hice no uso transacciones. Simplemente inserto en el setUp y borro en el tearDown.

    De todas formas, creo que es mejor con transacciones porque por ejemeplo, a mi cuando hago un debug y finalizo el hilo de ejecucion los datos de prueba no se borran. De todas formas, no pasa nada, porque en la siguiente ejecucion del test, dara error de duplicacion de claves al tratar de insertar, y luego borrara los datos que habias insertado en el anterior test, lo que dejara la base de datos como estaba.

  • 2. Raúl Vicente  |  septiembre 26th, 2007 at 10:19 pm

    El ejemplo me pareció una solución bastante elegante, de todas maneras también probaré la que tu me cuentas. Muchas Gracias.

  • 3. Alexis  |  agosto 4th, 2008 at 5:12 pm

    Souy nuevo en Spring no entiendo nada sinceramente me podrian enviar ejmplos basicos en mi msn please seria mucha ayuda grcias alexis.ad@hotmail.com

Comenta el articulo:

Requerido

Requerido,