Tutoriel sur une alternative à DBUnit : AssertJ-DB en 5 minutes

Régis POUILLER

AssertJ-DB (Assertions for database) peut être utilisé dans les tests unitaires avec une base de données en tant qu'alternative à DBUnit, en fournissant des assertions pour tester la valeur des données. Il permet donc de valider l'état de la base de données à la fin d'un test.

Une discussion a été ouverte pour les commentaires sur la publication de cet article : [ Commentez ♪

- Titre : Tutoriel sur une alternative à DBUnit : AssertJ-DB en 5 minutes

- Auteur : Régis POUILLER

- Parution : 3 septembre 2015

- Licence : Copyright ® 2015 Régis POUILLER. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Origine du projet

- III. Ressources

- IV. Versions des logiciels

- V. Un petit exemple simple

- VI. Conclusion

- VII. Remerciements

I - Introduction

AssertJ-DB (Assertions for database) peut être utilisé dans les tests unitaires avec une base de données en tant qu'alternative à DBUnit, en fournissant des assertions pour tester la valeur des données. Il permet donc de valider l'état de la base de données à la fin d'un test.

Cependant, à la différence de DBUnit, AssertJ-DB ne permet pas de charger de valeurs en début de test. Mais il existe une excellente solution permettant cela : DBSetup ( a été présenté sur developpez.com dans l'article « Tutoriel sur une introduction à DbSetup, une alternative à DbUnit » d'Antoine Rey).

Ainsi, AssertJ-DB n'est pas à lui seul une alternative à DBUnit. Mais il offre un moyen (voire des moyens) de contrôler les données dans la base à la fin d'un test effectuant une mise à jour. Il est donc un complément à DBSetup (qui lui ne contrôle pas les données).

Comme nous le verrons dans cet article, AssertJ-DB permet de contrôler les données de toutes sortes de manières (égalité, comparaison…). Nous verrons également qu'il comprend quelques concepts simples permettant d'aborder le test des données de multiples manières et d'ainsi apporter de la souplesse dans le choix du test à écrire.

II - Origine du projet

Lors de Devoxx 2014, j'ai assisté à la présentation « Le bon testeur il teste… et le mauvais testeur il teste aussi » d'Agnès Crepet et Guillaume Ehret.

Lors de cette conférence, ils nous ont présenté plusieurs outils, dont DBSetup (qui permet donc de charger/initialiser des données dans une base de données pour un test unitaire) et AssertJ (qui fournit un grand nombre d'assertions variées et très lisibles permettant d'avoir des tests très clairs et rapides à écrire).

Ne connaissant pas avant ces deux outils, je les ai trouvés très intéressants. Il y a juste que pour couvrir complètement mes besoins, il aurait fallu qu'un de ces outils (ou éventuellement un autre outil) permette également de contrôler les données à la fin d'un test : par exemple dans le cas d'une modification de données ou même pour vérifier qu'aucune donnée n'a été modifiée.

C'est là que m'est venue l'idée de rajouter un projet dans la « famille » des projets AssertJ (AssertJ Core, AssertJ Guava, AssertJ Neo4j, AssertJ Swing et AssertJ Joda-Time).

J'ai donc pris contact avec Joel Costigliola en charge d'AssertJ qui a accepté ma proposition de créer un nouveau projet et c'est ainsi que j'ai commencé la réalisation d'AssertJ-DB.

L'idée de départ était de m'inspirer des choses qui m'avaient plu dans ces deux projets pour essayer de créer un outil agréable permettant de réaliser rapidement et simplement des tests avec une base de données qui seraient impossibles ou fastidieux avec DBUnit : par exemple devoir créer tout un fichier XML comprenant plusieurs lignes pour tester une seule valeur ou le nombre de changements après l'exécution d'une DAO.

III - Ressources

En complément à cet article, vous pouvez trouver des informations dans la documentation officielle d'AssertJ-DB.

IV - Versions des logiciels

Les versions des principaux logiciels et bibliothèques utilisés dans cet article sont :

- Java : JDK 7 ;

- JUnit : 4.12 ;

- H2 : 1.4.187 ;

- DBSetup : 1.5.0 ;

- AssertJ-DB : 1.0.1.

Dans l'article, les tests unitaires utilisent JUnit. Les données sont dans une base H2 en mémoire dont la table est créée par accès JDBC. Ces données sont chargées en utilisant DBSetup.

V - Un petit exemple simple

Avant toutes choses, il convient de déclarer les dépendances :

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">

<modelVersion>4.0.0</modelVersion>
<groupId>com.developpez.com</groupId>
<artifactId>assertj-db-tutoriel</artifactId>
<version>1.0</version>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.187</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.ninja-squad</groupId>
<artifactId>DbSetup</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-db</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
</project>

Pour l'exemple, nous prendrons la table de données ci-dessous :

Table MEMBRES

ID

NOM

PRENOM

SURNOM

DATE_NAISSANCE

TAILLE

1

Hewson

Paul David

Bono

10/05/1960

1,75

2

Evans

David Howell

The Edge

08/08/1961

1,77

3

Clayton

Adam

13/03/1960

1,78

4

Mullen

Larry

31/10/1961

1,70

Ci-dessous le code de la classe de test pour notre premier exemple : la méthode setUpGlobal() crée la base de données en mémoire avec ses tables, la méthode setUp() charge les données grâce à DBSetup et pour finir la méthode testSimple() utilise AssertJ-DB afin de tester le contenu de la table MEMBRES.
Cette méthode effectue des tests qu'il est impossible de réaliser simplement avec DBUnit : contrôle du nombre de lignes de la table, du nom d'une colonne et de valeurs en particulier à des emplacements précis.

TestSimple.java

1. package com.developpez.rpouiller.assertj.db;

3. import static com.ninja_squad.dbsetup.Operations.deleteAllFrom;

4. import static com.ninja_squad.dbsetup.Operations.insertInto;

5. import static com.ninja_squad.dbsetup.Operations.sequenceOf;

7. import java.sql.Connection;

8. import java.sql.Date;

9. import java.sql.SQLException;

10. import java.sql.Statement;

12. import org.assertj.db.type.DateValue;

13. import org.assertj.db.type.Table;

14. import org.h2.jdbcx.JdbcConnectionPool;

15. import org.junit.Before;

16. import org.junit.BeforeClass;

17. import org.junit.Test;

19. import com.ninja_squad.dbsetup.DbSetup;

20. import com.ninja_squad.dbsetup.destination.DataSourceDestination;

21. import com.ninja_squad.dbsetup.operation.Operation;

23. import static org.assertj.db.api.Assertions.assertThat;

25. /**

26. * Premier test simple.

27. */

28. public class TestSimple {

30. private static JdbcConnectionPool dataSource;

32. /**

33. * Création de la source de données (globale à la classe).

34. * Utilisation de JDBC pour cette création.

35. * @throws SQLException

36. */

37. @BeforeClass

38. public static void setUpGlobal() throws SQLException {

39. if (dataSource == null) {

40. dataSource = JdbcConnectionPool.create("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "user", "password");

41. try (Connection connection = dataSource.getConnection()) {

42. try (Statement statement = connection.createStatement()) {

43. statement.executeUpdate("create table membres("

44. + "id number primary key, "

45. + "nom varchar not null, "

46. + "prenom varchar not null, "

47. + "surnom varchar, "

48. + "date_naissance date, "

49. + "taille decimal);");

50. }

51. }

52. }

53. }

55. /**

56. * Chargement des données en base (avant chaque test).

57. * Utilisation de DBSetup (effacement de toutes les données puis insertion).

58. */

59. @Before

60. public void setUp() {

61. Operation OperationInsert = insertInto("membres")

62. .columns("id", "nom", "prenom", "surnom", "date_naissance", "taille")

63. .values(1, "Hewson", "Paul David", "Bono", Date.valueOf("1960-05-10"), 1.75)

64. .values(2, "Evans", "David Howell", "The Edge", Date.valueOf("1961-08-08"), 1.77)

65. .values(3, "Clayton", "Adam", null, Date.valueOf("1960-03-13"), 1.78)

66. .values(4, "Mullen", "Larry", null, Date.valueOf("1961-10-31"), 1.7)

67. .build();

69. DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource),

70. sequenceOf(deleteAllFrom("membres"), OperationInsert));

72. dbSetup.launch();

73. }

75. /**

76. * Test simple utilisant AssertJ-DB.

77. */

78. @Test

79. public void testSimple() {

80. // Objet Table sur la table "membres" de la DataSource

81. Table table = new Table (dataSource, "membres");

83. // Vérifie que la table contient 4 enregistrements

84. assertThat(table).hasNumberOfRows(4);

85. // Vérifie que la valeur de la colonne "nom" de l'enregistrement à l'index 2

86. // est égal à la chaîne de caractères "Clayton"

87. assertThat(table).row(2).value("nom").isEqualTo("Clayton");

88. // Vérifie que la valeur à l'index 3 de la colonne "surnom" est null

89. assertThat(table).column("surnom").value(3).isNull();

90. // Vérifie que la colonne à l'index 4 a pour nom "date_naissance"

91. // puis que la valeur à l'index 1 de cette colonne

92. // est égal à la date 08/08/1960

93. assertThat(table).column(4).hasColumnName("date_naissance")

94. .value(1).isEqualTo(DateValue.of(1960, 8, 8));

95. }

96. }

Lorsqu'on lance le test, nous obtenons l'erreur suivante :

Erreur lors du test

java.lang.AssertionError: [Value at index 1 of Column at index 4 (column name : DATE_NAISSANCE) of membres table]
Expecting:
<1961-08-08>
to be equal to:
<1960-08-08>
at com.developpez.rpouiller.assertj.db.TestSimple.testSimple(TestSimple.java:93)
...

Nous remarquons qu'il y a une erreur dans le test, car l'année de naissance de « The Edge » est 1961. Nous corrigeons la méthode de test comme ci-dessous afin que le test passe correctement.

Méthode testSimple de TestSimple.java corrigée

1. /**

2. * Test simple utilisant AssertJ-DB.

3. */

4. @Test

5. public void testSimple() {

6. // Objet Table sur la table "membres" de la DataSource

7. Table table = new Table (dataSource, "membres");

9. // Vérifie que la table contient 4 enregistrements

10. assertThat(table).hasNumberOfRows(4);

11. // Vérifie que la valeur de la colonne "nom" de l'enregistrement à l'index 2

12. // est égal à la chaîne de caractères "Clayton"

13. assertThat(table).row(2).value("nom").isEqualTo("Clayton");

14. // Vérifie que la valeur à l'index 3 de la colonne "surnom" est null

15. assertThat(table).column("surnom").value(3).isNull();

16. // Vérifie que la colonne à l'index 4 a pour nom "date_naissance"

17. // puis que la valeur à l'index 1 de cette colonne

18. // est égal à la date 08/08/1961

19. assertThat(table).column(4).hasColumnName("date_naissance")

20. .value(1).isEqualTo(DateValue.of(1961, 8, 8));   // Ligne corrigée

21. }

22. }

Ce petit exemple relativement simple permet de voir quelques concepts de AssertJ-DB :

- les éléments :

- à la ligne 81, Table qui représente une table « MEMBRES » de la base de données,

- à la ligne 89, Column qui représente la colonne « SURNOM » de la table « MEMBRES »,

- à la ligne 87, Row qui représente l'enregistrement à l'index 2 de la table « MEMBRES »,

- à la ligne 87, Value qui représente la valeur de la colonne « NOM » de l'enregistrement à l'index 2 de la table « MEMBRES » ;

- la navigation :

- à la ligne 87, l'assertion commence au niveau de la table, « descend » au niveau de l'enregistrement d'index 2 puis « descend » encore au niveau de la valeur de la colonne « NOM » pour réaliser une assertion d'égalité,

- à la ligne 89, l'assertion commence au niveau de la table, « descend » au niveau de la colonne « SURNOM » puis « descend » encore au niveau de la valeur d'index 3 pour réaliser une assertion de nullité,

- à la ligne 93, l'assertion commence au niveau de la table, « descend » au niveau de la colonne d'index 4 pour réaliser une assertion sur le nom de la colonne puis « descend » encore au niveau de la valeur d'index 1 pour réaliser une assertion d'égalité ;

- DateValue : AssertJ-DB fonctionne avec Java 7. Donc, ne disposant pas de la dernière API de date de Java 8, AssertJ-DB fournit un moyen de comparer facilement les valeurs avec des dates, heures et dates/heures ;

- les assertions : les assertions ne sont pas un concept propre à AssertJ-DB, mais cet exemple permet de voir qu'il en existe plusieurs.

VI - Conclusion

Comme nous avons pu en avoir un aperçu rapide, AssertJ-DB offre plusieurs possibilités de test.
Bien que cela ne soit pas montré dans cet article d'introduction, il permet notamment de réaliser les tests suivants :

- tester les valeurs d'un enregistrement ou d'une colonne ;

- comparer une valeur avec un nombre, une date, une heure ou une date/heure ;

- tester le nombre d'enregistrements ;

- limiter le test sur les changements dans la base de données (dans un intervalle de temps) ;

- tester le nombre de ces changements, leur type, les valeurs ayant changé ;

- etc.

Par ailleurs, AssertJ-DB est un projet actif : plusieurs évolutions sont déjà prévues.

VII - Remerciements

Je remercie très sincèrement :

- www.developpez.com qui me permet de publier cet article ;

- Nono40 et djibril pour leurs outils ;

- Mickael Baron pour sa relecture technique ;

- Claude Leloup pour sa relecture orthographique.

Je remercie également Joel Costigliola pour son accueil et son aide dans le développement de mon projet.