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 :
<?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.
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.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
package
com.developpez.rpouiller.assertj.db;
import
static
com.ninja_squad.dbsetup.Operations.deleteAllFrom;
import
static
com.ninja_squad.dbsetup.Operations.insertInto;
import
static
com.ninja_squad.dbsetup.Operations.sequenceOf;
import
java.sql.Connection;
import
java.sql.Date;
import
java.sql.SQLException;
import
java.sql.Statement;
import
org.assertj.db.type.DateValue;
import
org.assertj.db.type.Table;
import
org.h2.jdbcx.JdbcConnectionPool;
import
org.junit.Before;
import
org.junit.BeforeClass;
import
org.junit.Test;
import
com.ninja_squad.dbsetup.DbSetup;
import
com.ninja_squad.dbsetup.destination.DataSourceDestination;
import
com.ninja_squad.dbsetup.operation.Operation;
import
static
org.assertj.db.api.Assertions.assertThat;
/**
* Premier test simple.
*/
public
class
TestSimple {
private
static
JdbcConnectionPool dataSource;
/**
* Création de la source de données (globale à la classe).
* Utilisation de JDBC pour cette création.
*
@throws
SQLException
*/
@BeforeClass
public
static
void
setUpGlobal
(
) throws
SQLException {
if
(
dataSource ==
null
) {
dataSource =
JdbcConnectionPool.create
(
"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"
, "user"
, "password"
);
try
(
Connection connection =
dataSource.getConnection
(
)) {
try
(
Statement statement =
connection.createStatement
(
)) {
statement.executeUpdate
(
"create table membres("
+
"id number primary key, "
+
"nom varchar not null, "
+
"prenom varchar not null, "
+
"surnom varchar, "
+
"date_naissance date, "
+
"taille decimal);"
);
}
}
}
}
/**
* Chargement des données en base (avant chaque test).
* Utilisation de DBSetup (effacement de toutes les données puis insertion).
*/
@Before
public
void
setUp
(
) {
Operation OperationInsert =
insertInto
(
"membres"
)
.columns
(
"id"
, "nom"
, "prenom"
, "surnom"
, "date_naissance"
, "taille"
)
.values
(
1
, "Hewson"
, "Paul David"
, "Bono"
, Date.valueOf
(
"1960-05-10"
), 1.75
)
.values
(
2
, "Evans"
, "David Howell"
, "The Edge"
, Date.valueOf
(
"1961-08-08"
), 1.77
)
.values
(
3
, "Clayton"
, "Adam"
, null
, Date.valueOf
(
"1960-03-13"
), 1.78
)
.values
(
4
, "Mullen"
, "Larry"
, null
, Date.valueOf
(
"1961-10-31"
), 1.7
)
.build
(
);
DbSetup dbSetup =
new
DbSetup
(
new
DataSourceDestination
(
dataSource),
sequenceOf
(
deleteAllFrom
(
"membres"
), OperationInsert));
dbSetup.launch
(
);
}
/**
* Test simple utilisant AssertJ-DB.
*/
@Test
public
void
testSimple
(
) {
// Objet Table sur la table "membres" de la DataSource
Table table =
new
Table (
dataSource, "membres"
);
// Vérifie que la table contient 4 enregistrements
assertThat
(
table).hasNumberOfRows
(
4
);
// Vérifie que la valeur de la colonne "nom" de l'enregistrement à l'index 2
// est égal à la chaîne de caractères "Clayton"
assertThat
(
table).row
(
2
).value
(
"nom"
).isEqualTo
(
"Clayton"
);
// Vérifie que la valeur à l'index 3 de la colonne "surnom" est null
assertThat
(
table).column
(
"surnom"
).value
(
3
).isNull
(
);
// Vérifie que la colonne à l'index 4 a pour nom "date_naissance"
// puis que la valeur à l'index 1 de cette colonne
// est égal à la date 08/08/1960
assertThat
(
table).column
(
4
).hasColumnName
(
"date_naissance"
)
.value
(
1
).isEqualTo
(
DateValue.of
(
1960
, 8
, 8
));
}
}
Lorsqu'on lance le test, nous obtenons l'erreur suivante :
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.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
/**
* Test simple utilisant AssertJ-DB.
*/
@Test
public
void
testSimple
(
) {
// Objet Table sur la table "membres" de la DataSource
Table table =
new
Table (
dataSource, "membres"
);
// Vérifie que la table contient 4 enregistrements
assertThat
(
table).hasNumberOfRows
(
4
);
// Vérifie que la valeur de la colonne "nom" de l'enregistrement à l'index 2
// est égal à la chaîne de caractères "Clayton"
assertThat
(
table).row
(
2
).value
(
"nom"
).isEqualTo
(
"Clayton"
);
// Vérifie que la valeur à l'index 3 de la colonne "surnom" est null
assertThat
(
table).column
(
"surnom"
).value
(
3
).isNull
(
);
// Vérifie que la colonne à l'index 4 a pour nom "date_naissance"
// puis que la valeur à l'index 1 de cette colonne
// est égal à la date 08/08/1961
assertThat
(
table).column
(
4
).hasColumnName
(
"date_naissance"
)
.value
(
1
).isEqualTo
(
DateValue.of
(
1961
, 8
, 8
)); // Ligne corrigée
}
}
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.