DBUnit

Introduction

DBUnit est une API (extension de Junit) destinée aux projets qui exploitent des bases de données. Cet outil permet de tester du code gérant des accès aux bases de données. Il permet également de résoudre les problèmes qui peuvent survenir lorsqu’un test corrompt la base de données et entraîne ensuite un échec des tests unitaires. Ainsi, elle permet avant chaque test, d’initialiser la base de données dans un état prédéfini à l’aide de fichiers XML. Après le déroulement d’un test, d’autres fichiers du même format peuvent servir pour vérifier les données enregistrées.

DBUnit a la capacité d’exporter et d’importer une base de données vers un fichier ou à partir d’un fichier XML de jeu de données. Depuis la version 2.0, DBUnit peut aussi travailler avec des jeux de données très grands quand il est utilisé en mode “streaming”. DBUnit peut aussi aider à vérifier que la base de données correspond à des valeurs précises attendues pour un jeu de données.

Tutoriel

Nous allons voir dans ce tutoriel de manière très simple comment on peut mettre en place des jeux de données pour effectuer des tests unitaires grâce à DBUnit.

Création du projet dans Eclipse

  • Lancer Eclipse. Si Eclipse n’est pas installé, il faut aller voir la partie installation : scub-foundation
  • Lors du lancement d’Eclipse, il va demander de sélectionner le “workspace”, autrement dit l’espace de travail, c’est à dire le répertoire dans lequel il doit aller chercher les ressources. On peut laisser l’emplacement par défaut, c’est dans ce répertoire que seront créées par défaut toutes les ressources (projets, etc…) par la suite.
  • Lors du premier lancement une perspective d’accueil est affichée. Il faut cliquer sur Go to the Workbench
  • On crée un nouveau projet. Pour cela on ouvre le menu File→ New→Java Project
  • La fenêtre de wizard apparaît alors. Nous allons simplement indiquer ici le nom du projet dans le champ Project name : sf-sample-dbunit
  • On peut ensuite cliquer sur Finish. Le projet apparaît alors dans l’onglet Package qui répertorie les différents projets du workspace.

Ajout des librairies

  • Il faut télécharger les jar de JUnit, DBUnit, log4j: junit-4.5.jar, dbunit-2.4.8.jar et log4j-1.2.15.jar
  • Et les archives de HSQLDB et Commons IO contenant les jar hsqldb.jar et commons-io-1.4.jar : hsqldb-2.2.8.zip, commons-io-1.4.jar
  • De plus, il faut récupérer les jar slf4-api-1.5.0.jar et slf4j-log12-1.5.0.jar contenus dans le dossier spring-framework-2.5.6 téléchargé précédemment.
  • On déplace les jar dans notre projet. Pour ce faire, on ouvre un explorateur de fichiers, pour accéder au workspace (sous Ubuntu il sera situé par défaut dans /home/user/workspace, et sous Windows il sera par défaut dans …user/MesDocuments/workspace), on va dans le répertoire du projet, dans lequel on créé un nouveau répertoire lib qui contiendra les librairies. On copie ensuite les fichiers que l’on vient de récupérer dedans.
  • Il faut ensuite spécifier à Eclipse dans notre projet qu’on a ajouté une librairie pour pouvoir l’utiliser. On fait un clic droit sur le projet dans le package explorer qui ouvre un menu contextuel : on ouvre Build Path > Configure Build Path. On clic sur l’onglet Libraries, puis sur le bouton add JARS…. Il suffit ensuite d’ajouter les bibliothèques suivantes junit-4.5.jar, dbunit-2.4.8.jar, hsqldb.jar, log4j-1.2.15.jar, commons-io-1.4.jar, slf4-api.1.5.0.jar et slf4j-log12-1.5.0.jar situées dans lib du projet sf-sample-dbunit. On valide les modifications sur le projet en cliquant sur OK. Nous sommes maintenant prêts à débuter le code!

Implémentation

Nous allons volontairement ici adopter une structure très simple pour ce tutoriel. Nous n’aurons à créer qu’une seule classe de tests unitaires, qui va charger un jeu de données à partir d’un fichier XML, effectuer quelques tests basiques, et ensuite montrer comment exporter le jeu de données dans un nouveau fichier XML.

Création du jeu de données

Nous allons commencer par écrire le script de création de la base.
Pour cela, on commence par créer un nouveau répertoire à la racine du projet :

  • clic droit sur le nom du projet dans le package explorer, New → Folder
  • On indique comme nom db
  • On valide en cliquant sur Finish

On crée le nouveau fichier dans le répertoire :

  • clic droit sur le répertoire db dans le package explorer, new → File
  • On indique le nom du nouveau fichier dans le champ File name le nom : test.script

Une fois le fichier créé, il faut maintenant le remplir avec le script suivant. Cette base permettra de stocker les grands constructeurs informatiques :

CREATE SCHEMA PUBLIC AUTHORIZATION DBA
CREATE MEMORY TABLE MANUFACTURER(ID INTEGER,NAME VARCHAR(25))
CREATE USER SA PASSWORD ""
GRANT DBA TO SA
SET WRITE_DELAY 10
SET SCHEMA PUBLIC
INSERT INTO MANUFACTURER VALUES(1,'IBM')
INSERT INTO MANUFACTURER VALUES(2,'Dell')
INSERT INTO MANUFACTURER VALUES(3,'Apple')

Maintenant que nous avons le script de création de base, nous allons créer le fichier XML qui contiendra le jeu de données à utiliser pour notre tutoriel.
Pour cela, on commence par créer un nouveau répertoire à la racine du projet :

  • clic droit sur le nom du projet dans le package explorer, New → Folder
  • On indique comme nom dataSets
  • On valide en cliquant sur Finish

On crée ensuite le nouveau fichier dans le répertoire :

  • clic droit sur le répertoire dataSets dans le package explorer, New → Other
  • Dans le wizard on sélectionne XML → XML, ou on peut taper XML directement dans le champ de recherche
  • On clic ensuite sur Next
  • On indique dans le champ File name le nom : inputFlatXmlDataSet.xml
  • On valide en cliquant sur Finish

Le fichier XML est maintenant créé, il reste à le compléter pour remplir notre base et avoir un jeu de données prêt à l’emploi pour nos tests.
Voici le contenu du fichier ci-dessous :

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
  <MANUFACTURER ID="1" NAME="IBM"/>
  <MANUFACTURER ID="2" NAME="Dell"/>
  <MANUFACTURER ID="3" NAME="Apple"/>
  <MANUFACTURER ID="4" NAME="Asus"/>
</dataset>

Création du test unitaire

Nous allons implémenter notre test unitaire qui va hériter non pas d’une super classe de JUnit mais de DBUnit : DBTestCase

  • On se place sur le répertoire src du projet sf-sample-dbunit, puis on fait un clic droit, New > Class.
  • La fenêtre de wizard New Java Class est normalement apparue. On indique dans le champ Package com.scub.foundation.samples.dbunit.test et on entre dans Name le nom TestDBUnit puis on clique sur Finish.
  • Voici le code de la classe TestDBUnit commentée ci-dessous :
package com.scub.foundation.samples.dbunit.test;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.spi.RootLogger;
import org.dbunit.Assertion;
import org.dbunit.DBTestCase;
import org.dbunit.PropertiesBasedJdbcDatabaseTester;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.Test;
 
/**
 * Classe de test unitaire.
 * @author Scub-Foundation
 */
public class TestDBUnit extends DBTestCase {
 
    /** Logger. */
    private static final Logger LOGGER = RootLogger.getLogger(DBTestCase.class);
 
    /** Driver JDBC. */
    private static final String JDBC_DRIVER = "org.hsqldb.jdbcDriver";
 
    /** Base de données HSQLDB nommée "database" qui fonctionne en mode mémoire. */
    private static final String DATABASE = "jdbc:hsqldb:file:db/test";
 
    /** Utilisateur qui se connecte à la base de données. */
    private static final String USER = "sa";
 
    /** GetDataSet mot de passe pour se connecter à la base de données. */
    private static final String PASSWORD = "";
 
    /** Nom du fichier xml contenant le jeu de données à importer. */
    private static final String INPUT_DATA_SET_FILENAME = "dataSets/inputFlatXmlDataSet.xml";
 
    /** Nom du fichier xml qui contiendra le jeu de données exporté. */
    private static final String OUTPUT_DATA_SET_FILENAME = "dataSets/outputFlatXmlDataSet.xml";
 
    /** Nom de la table. */
    public static final String TABLE_NAME = "MANUFACTURER";
 
    // Variables de test
    /** Le nombre de tuples présents dans la table. */
    private static final int ROW_COUNT = 4;
 
    /**
     * Constructeur.
     * @param name
     * @throws Exception
     */
    public TestDBUnit(String name) {
        super(name);
        System.setProperty(
                PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS,
                JDBC_DRIVER);
        System.setProperty(
                PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL,
                DATABASE);
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME,
                USER);
        System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD,
                PASSWORD);
    }
 
    /**
     * Override method to set custom properties/features {@inheritDoc}.
     */
    @Override
    protected void setUpDatabaseConfig(DatabaseConfig config) {
        super.setUpDatabaseConfig(config);
        config.setProperty(DatabaseConfig.PROPERTY_BATCH_SIZE, new Integer(97));
        config.setFeature(DatabaseConfig.FEATURE_BATCHED_STATEMENTS, true);
    }
 
    /**
     * Charge le jeu de données à partir d'un fichier XML d'import.
     */
    @Override
    protected IDataSet getDataSet() throws Exception {
        FlatXmlDataSet loadedDataSet = new FlatXmlDataSet(new FileInputStream(
                INPUT_DATA_SET_FILENAME));
        return loadedDataSet;
 
    }
 
    /**
     * Méthode étant appelée au début de chaque test.
     */
    @Override
    protected DatabaseOperation getSetUpOperation() throws Exception {
        DatabaseOperation.CLEAN_INSERT.execute(getConnection(), getDataSet());
        return DatabaseOperation.NONE;
    }
 
    /**
     * Méthode étant appelée après à la fin de chaque test.
     */
    @Override
    protected DatabaseOperation getTearDownOperation() throws Exception {
        return DatabaseOperation.NONE;
    }
 
    /**
     * Retourne le jeu de données chargé dans la base de données.
     */
    protected QueryDataSet getDatabaseDataSet() throws Exception {
        QueryDataSet loadedDataSet = new QueryDataSet(getConnection());
        loadedDataSet.addTable(TABLE_NAME);
 
        return loadedDataSet;
    }
 
    /**
     * Méthode qui vérifie que le jeu de données a correctement été chargé.
     * @throws Exception
     */
    @Test
    public void testCheckDataLoaded() throws Exception {
        // On récupère notre jeu de données
        IDataSet databaseDataSet = getDatabaseDataSet();
        // On vérifie que le jeu de données n'est pas nul.
        assertNotNull(databaseDataSet);
        // On compte combien d'enregistrement contient la table
        int rowCount = databaseDataSet.getTable(TABLE_NAME).getRowCount();
        // On le compare avec le nombre d'enregistrement connu
        assertEquals("Le nombre d'enregistrements dans la table \""
                + TABLE_NAME + "\" ne correspond pas", ROW_COUNT, rowCount);
    }
 
    /**
     * Montre comment des données peuvent être extraites pour être comparées
     * avec la représentation XML.
     * @throws Exception
     */
    @Test
    public void testCompareTable() throws Exception {
 
        // Récupère la base de données après avoir exécuté le code
        IDataSet databaseDataSet = getDatabaseDataSet();
        ITable actualTable = databaseDataSet.getTable(TABLE_NAME);
 
        // Charge les données attendues à partir du jeu de données du fichier
        // XML
        IDataSet expectedDataSet = new FlatXmlDataSet(new File(
                INPUT_DATA_SET_FILENAME));
        ITable expectedTable = expectedDataSet.getTable(TABLE_NAME);
 
        // Compare les deux tables pour vérifier qu'elles sont bien identiques
        Assertion.assertEquals(expectedTable, actualTable);
    }
 
    /**
     * Test de comparaison des données avec une requête genérée par IDataSet.
     * @throws Exception
     */
    @Test
    public void testCompareQuery() throws Exception {
        IDataSet loadedDataSet = getDataSet();
        // Préparation d'un jeu de données pour effectuer une requête
        QueryDataSet queryDataSet = new QueryDataSet(getConnection());
        queryDataSet.addTable(TABLE_NAME);
 
        // On compare que le jeu de données original chargé est identique à la
        // requête qu'on vient d'effectuer
        Assertion.assertEquals(loadedDataSet, queryDataSet);
    }
 
    /**
     * Teste le mécanisme d'exportation de DBUnit.
     * @throws Exception
     */
    @Test
    public void testExportData() throws Exception {
        // On récupère le jeu de données du fichier XML
        IDataSet dataSet = getDatabaseDataSet();
 
        // Fichier XML du jeu de données d'import
        File inputFile = new File(INPUT_DATA_SET_FILENAME);
        // On vérifie que le fichier existe
        assertNotNull(inputFile);
        // Fichier XML du jeu de données d'export
        File outputFile = new File(OUTPUT_DATA_SET_FILENAME);
        FlatXmlDataSet.write(dataSet, new FileOutputStream(outputFile));
 
        // On compare les deux fichiers XML pour vérifier qu'ils sont identiques
        String inputDataSetString = FileUtils.readFileToString(inputFile,
                "UTF8").replace("  ", "\t").trim();
        String outputDataSetString = FileUtils.readFileToString(outputFile,
                "UTF8").replace("  ", "\t").trim();
        assertEquals(inputDataSetString, outputDataSetString);
 
    }
}

Ce test unitaire montre qu’on peut avec DBUnit facilement charger un jeu de données à partir d’un fichier XML, le manipuler et ensuite exporter le tout dans un nouveau fichier XML.

Télécharger les sources

Les sources du projet Eclipse pour ce tutoriel sont téléchargeables sur Source Forge à l’adresse suivante.