Utilisation de DBUnit et h2Auteur : Julien Gauchet le 04/07/2017 (1 revision)

DBUnit est une extension du framework JUnit permettant d'écrire des cas de tests en manipulant des bases de données. Il permet d'initialiser une base de données et de charger des données.

1. Mise en place dans le projet

Pour utiliser DBUnit et h2, il suffit d'ajouter les dépendances suivantes dans le fichier pom.xml

<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>1.4.195</version>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>9.4-1200-jdbc41</version>
</dependency>

Les test vont également utiliser le jdbc utilisé par le projet. Dans le cas où nous utilisons une base de données postgre, nous aurons besoin de la dépendance suivante :

<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
	<version>9.4-1200-jdbc41</version>
</dependency>

2. Initialisation d'une base

2.1. Connexion à une base h2

L'initialisation d'une connexion à une base H2 est très simple, il suffit d'écrire le code suivant :

JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
dataSource.setUser("sa");
dataSource.setPassword("");
try {
	cnx = dataSource.getConnection();
}
catch (SQLException e) {
	e.printStackTrace();
}

Nous disposons alors d'une connexion vers une base de données vide, à ce moment là, nous pouvons nous contenter d'utiliser h2 sans faire appel à DBUnit et à créer des tables via le code de notre application

2.2. Création de la structure

Il est possible de créer la structure de la base de données à l'aide de DBUnit, dans le cas d'une base h2, cette création est obligatoire, mais si nous avions utilisé une connexion à une base de données de test, les tables pourraient préexister. Pour initialiser la structure de la base de données, on fait exécuter à DBUnit un script sql de la manière suivante :

File init=new File("src/test/resources/inti_database.sql");
try (InputStreamReader fr = new FileReader(init); BufferedReader br = new BufferedReader(fr)) {
	RunScript.execute(cnx, br);
}

Notons que le script entier est exécuté et qu'il pourrait également contenir des INSERT pour ajouter des données dans la base.

2.3. Chargement des dataset

Si vous ne savez pas comment écrire des requêtes INSERT ou si vous avez envie d'utiliser un mécanisme de chargement de données basé sur des fichiers xml dont le fonctionnement présente quelques subtilités, vous pouvez utliser les dataset. Dans tous les autres cas, mieux vaut s'en passer et utiliser un script contenant des INSERT et le charger comme présenté ci-dessus.

Les dataset présentent le contenu de la base de données sous forme de fichier xml. Par exemple :

<?xml version="1.0" encoding="UTF-8"?>  
<dataset>  
	<table1 id="1" libelle="A" />
    <table2 id="2" id_table_1="1" />
</dataset>

Le chargement des dataset se fait après la création de la structure de la base et de la manière suivante :

IDatabaseConnection dbUnitConnection = new DatabaseConnection(cnx);
File init=new File("src/test/resources/inti_database.sql");
try (InputStreamReader fr = new FileReader(init); BufferedReader br = new BufferedReader(fr)) {
	RunScript.execute(cnx, br);
}
FlatXmlDataSetBuilder xmlDSBuilder = new FlatXmlDataSetBuilder();
xmlDSBuilder.setCaseSensitiveTableNames(false);
InputStream inputStreamXML = new FileInputStream("src/test/resources/dataset.xml");
IDataSet dataSet = xmlDSBuilder.build(inputStreamXML);
DatabaseOperation.CLEAN_INSERT.execute(dbUnitConnection, dataSet);

3. Exemple de classe de test

Vous l'aurez compris, je ne préconise pas d'utiliser les dataset qui posent plus de problèmes qu'elles n'en résolvent. L'exemple suivant ne se servira donc pas des dataset.

Nous voulons tester une DAO accédant à la table prenoms qui contient un ensemble de prénom. Cette dao contient les méthodes selectionnerPrenoms() qui permet de sélectionner le contenu de la table et insererPrenom() qui permet d'ajouter un prénom dans la base

package fr.julien.formation.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
 * Classe permettant d'accéder à la table prenoms
 * @author Julien Gauchet
 */
public class PrenomDao {

    private static PrenomDao instance = new PrenomDao();
    private static final String REQUETE_SELECT = "SELECT * FROM prenoms";
    private static final String REQUETE_INSERT = "INSERT INTO prenoms (prenom) VALUES (?)";

    private PrenomDao() {
        super();
    }

    /**
     * Méthode permettant de récupérer l'ensemble des prénoms de la base
     * @param cnx
     *            La connexion à la base de données
     * @return la liste des prénoms
     * @throws SQLException
     */
    public List selectionnerPrenoms(Connection cnx) throws SQLException {
        List res = new ArrayList<>();
        try (Statement stmt = cnx.createStatement(); ResultSet rs = stmt.executeQuery(REQUETE_SELECT)) {
            while (rs.next()) {
                res.add(rs.getString("prenom"));
            }
        }
        return res;
    }

    /**
     * Méthode permettant d'insérer un prénom
     * @param cnx
     *            La connexion à la base de données
     * @param prenom
     *            Le prénom à insérer
     * @throws SQLException
     */
    public void insererPrenom(Connection cnx, String prenom) throws SQLException {
        try (PreparedStatement stmt = cnx.prepareStatement(REQUETE_INSERT)) {
            stmt.setString(1, prenom);
            stmt.executeUpdate();
        }
    }

    /**
     * Méthode permettant d'accéder à l'instance de {@link PrenomDao}
     * @return l'instance de {@link PrenomDao}
     */
    public static PrenomDao getInstance() {
        return instance;
    }
}

Pour initialiser notre base de test, nous allons utiliser un fichier sql contenant les instructions suivantes :

DROP TABLE IF EXISTS prenoms;

CREATE TABLE prenoms(
	prenom VARCHAR
);

INSERT INTO prenoms
VALUES
	('Coralie'), 
	('Sébastien');

La classe de test sera alors la suivante :

package fr.julien.formation.dao;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.List;

import org.h2.jdbcx.JdbcDataSource;
import org.h2.tools.RunScript;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class PrenomDaoTest {

    private Connection cnx;

    @Before
    public void setUp() {
        JdbcDataSource dataSource = new JdbcDataSource();
        dataSource.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
        dataSource.setUser("sa");
        dataSource.setPassword("");
        try {
            cnx = dataSource.getConnection();
            try (InputStreamReader fr = new FileReader(new File("src/test/resources/init_database_prenoms.sql")); BufferedReader br = new BufferedReader(fr)) {
                RunScript.execute(cnx, br);
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testerDao() {
        try {
            List prenoms = PrenomDao.getInstance().selectionnerPrenoms(cnx);
            Assert.assertEquals(2, prenoms.size());
            PrenomDao.getInstance().insererPrenom(cnx, "Julien");
            try (Statement stmt = cnx.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM prenoms WHERE prenom='Julien'")) {
                Assert.assertTrue(rs.next());
            }
        }
        catch (Exception e) {
            e.printStackTrace();
            Assert.fail();
        }
    }
}

4. Une remarque personnelle

L'utilisation de DBUnit couplé avec une base h2 pose plusieurs problèmes :

  • Une base de données h2 n'est pas une base de données postgresql, certaines requêtes ne fonctionneront donc pas, et les types de données sont différents : le format text n'existe pas, COPY n'existe pas
  • L'initialisation de données via DBUnit nous oblige à gérer nos dataset en plus de nos requêtes insert et n'est pas performat dans le cadre d'une maintenance
  • La modification de la structure de la base de données ne sera pas répercutée sur la structure de la base de test, le travail à réaliser le cas échéant peut être long et complexe

Pour toutes ces raisons, je vous encourage à ne pas utiliser ni dbunit ni h2. Le plus intéressant est de gérer la construction de la base de données dans les programmes java et d'utiliser un schéma dédié pour les tests.