Contenido

Pruebas con Bases de Datos en memoria: DBUnit + HSQLDB

DBUnit es un sistema sencillo que nos permite cargar con datos nuestra base de datos con el fin de realizar tests con estos datos.

La gran ventaja de DBUnit es poder utilizar un sistema sencillo para indicar los datos a insertar mediante un XML, así como poder exportar una BBDD existente.

Sin embargo, DBUnit no permite la creación de la BBDD, lo que puede ser un problema al intentar usar una BBDD en memoria, como pueda ser HyperSQL o Derby.

Java

Enunciado

Como me gusta resolver los problemas concretos y no los abstractos, veamos cómo usar DBUnit con una base de datos en memoria. Para ello utilizaremos un pequeño gestor de libros con una única tabla, que contendrá el id, título y autor.

Datos

Nuestros datos serán los siguientes:

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book id="1" title="Fundación" author="Isaac Asimov"/>
    <book id="2" title="Clean Code" author="Robert C. Martin"/>
    <book id="3" title="Diseño ágil con TDD" author="Carlos Ble"/>
</dataset>

Aquí ya estamos diseñando. Lo que le estamos diciendo a DBUnit es que vamos a tener una tabla “book” con 3 columnas, que son “id”, “title” y “author”.

El archivo anterior se situará en el paquete “tests”, de manera que se empaquetará en el JAR con nuestras pruebas.

Código

Veamos ahora cómo indicárselo a DBUnit:

 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
32
33
34
35
36
37
38
39
40
public class test1 extends DBTestCase {
    static
    {
        try {
            String driver = "org.hsqldb.jdbcDriver";
            String url = "jdbc:hsqldb:mem:sample";
            String user ="sa";
            String pass = "";

            System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, driver);
            System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, user);
            System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, pass);

            Class.forName(driver);
            Connection con = DriverManager.getConnection(url, user, pass);
            st.execute("create table book ("
                    + "id integer, "
                    + "title varchar(50), "
                    + "author varchar(50)"
                    + ")");
            con.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected IDataSet getDataSet() throws Exception {
        InputStream file = getClass().getResourceAsStream("/tests/test1.xml");
        IDataSet result = new FlatXmlDataSet(file);
        return result;
    }

    @Test
    public void test1() throws Exception {
        IDatabaseConnection conn = getConnection();
        assertEquals(3, conn.getRowCount("book"));
        conn.close();
    }
}

Desglosando el código

Veamos ahora qué es lo que hemos hecho.

Lo primero es indicar que nuestro código hereda de DBTestCase. Eso nos obliga a implementar el método abstracto getDataSet. Este método le indica a DBUnit los datos con los que rellenar la BBDD antes de cada test.

1
2
3
4
5
6
@Override
protected IDataSet getDataSet() throws Exception {
    InputStream file = getClass().getResourceAsStream("/tests/test1.xml");
    IDataSet result = new FlatXmlDataSet(file);
    return result;
}

Para ello, cargamos el XML anterior como un “FlatXML”. Como se encuentra dentro de nuestro propio jar, utilizamos getResourceAsStream.

Ahora es necesario inicializar la conexión a BBDD. Como vamos a utilizar todos los argumentos varias veces, los guardamos en variables:

1
2
3
4
String driver = "org.hsqldb.jdbcDriver";
String url = "jdbc:hsqldb:mem:sample";
String user ="sa";
String pass = "";

Es importante rellenar el usuario, ya que DBUnit lo requiere. Como véis, utilizamos una base de datos en memoria de tipo HyperSQL .

Ahora inicializamos DBUnit. Para ello, establecemos variables de entorno:

1
2
3
4
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, driver);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, url);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, user);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, pass);

Con esto sería suficiente como para que DBUnit pueda ejecutarse sin problemas, pero la tabla “Book” no existe. Así que lo solucionamos con el acceso estándar a BBDD de Java:

1
2
3
4
5
6
7
8
9
Class.forName(driver);
Connection con = DriverManager.getConnection(url, user, pass);
Statement st = con.createStatement();
st.execute("create table book ("
        + "id integer, "
        + "title varchar(50), "
        + "author varchar(50)"
        + ")");
con.close();

Y ya está todo listo para nuestros tests.

1
2
3
4
5
6
@Test
public void test1() throws Exception {
    IDatabaseConnection conn = getConnection();
    assertEquals(3, conn.getRowCount("book"));
    conn.close();
}

En ese caso, obtenemos la conexión (al heredar de DBTestCase tenemos acceso al método getConnection) y comparamos el número de filas en “book” con 3.

Volcado de BBDD

Lo normal es crear la BBDD que deseamos probar y volcar los datos. Para ello podemos utilizar lo siguiente:

1
2
3
4
5
6
7
@Test
public void test_volcar_flatxml() throws Exception {
    IDatabaseConnection conn = getConnection();
    IDataSet dataset = conn.createDataSet();
    FlatXmlDataSet.write(dataset, System.out);
    conn.close();
}

Además, existe otro formato para el XML utilizado, solo que puede resultar demasiado grande (aunque, técnicamente, más correcto):

1
2
3
4
5
6
7
@Test
public void test_volcar_xml() throws Exception {
    IDatabaseConnection conn = getConnection();
    IDataSet dataset = conn.createDataSet();
    XmlDataSet.write(dataset, System.out);
    conn.close();
}

Que generará algo como:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version=''1.0'' encoding=''UTF-8''?>
<dataset>
  <table name="BOOK">
    <column>ID</column>
    <column>TITLE</column>
    <column>AUTHOR</column>
    <row>
      <value>1</value>
      <value>Fundación</value>
      <value><![CDATA[Isaac Asimov]]></value>
    </row>
    <row>
      <value>2</value>
      <value><![CDATA[Clean Code]]></value>
      <value><![CDATA[Robert C. Martin]]></value>
    </row>
    <row>
      <value>3</value>
      <value><![CDATA[Diseño Ágil con TDD]]></value>
      <value><![CDATA[Carlos Ble]]></value>
    </row>
  </table>
</dataset>

Comparando tablas

Todo lo anterior está muy bien, pero lo que de verdad nos interesa es comparar datos. El uso habitual es:

  • Se cargan unos datos,
  • se realizan operaciones,
  • se comprueba que los datos son correctos.

Pues esto resulta una tarea sencilla cuando se utiliza dbunit:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Test
public void test_comparar_tablas() throws Exception {
    IDatabaseConnection conn = getConnection();
    InputStream file = getClass().getResourceAsStream("/tests/test2.xml");
    IDataSet expected = new FlatXmlDataSet(file);
    ITable table = conn.createQueryTable( "books",
            "select * from book where author != ''Isaac Asimov''");
    Assertion.assertEquals(expected.getTable("book"), table);
    conn.close();
}

Como se ve, estamos cargamos un nuevo archivo de datos (test2.xml), realizamos una consulta y comparamos los resultados.

El archivo de datos cargado tendrá esta forma (en el paquete “tests”):

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
    <book id="2" title="Clean Code" author="Robert C. Martin"/>
    <book id="3" title="Diseño Ágil con TDD" author="Carlos Ble"/>
</dataset>

A tener en cuenta

Hay varios puntos a tener en cuenta:

  • Si nuestros tests no comienzan por test, no se ejecutarán (formato de junit 3).
  • Podemos utilizar una BBDD en memoria para probar datos para no modificar la BBDD real. Además, resultará más rápido y cómodo.
  • Cuanto más pequeños sean los archivos de pruebas, serán menos propensos a cambios y más rápidos de ejecutar

Más información

Tenéis más información en la web de DBUnit , en la de JUnit y en la de HyperSQL . También podría haberse hecho con Derby , como ya comenté.