Lorsqu’on écrit nos tests unitaires, on peut avoir l’impression que certains cas de tests sont quasiment identiques dans leur structure. Ils sont rébarbatifs à lire/écrire. C’est là que la notion de dataProvider est utile: on va réutiliser le même test mais avec des paramètres d’entrée différents 😉
On a le premier joueur qui marque x fois, le second joueur qui marque x fois et enfin on compare avec le résultat attendu.
Les tests peuvent vite se multiplier et se ressembler:
@Test
void shouldBe15LoveWhenPlayerOneScore() {
Game game = new Game();
game.playerOneScoresAPoint();
String score = game.printScore();
assertThat(score).isEqualTo("15 - LOVE");
}
@Test
void shouldBe15-15WhenBothPlayersScore() {
Game game = new Game();
game.playerOneScoresAPoint();
game.playerTwoScoresAPoint();
String score = game.printScore();
assertThat(score).isEqualTo("15 - 15");
}
@Test
void shouldBe30LoveWhenPlayerOneScore() {
Game game = new Game();
game.playerOneScoresAPoint();
game.playerOneScoresAPoint();
String score = game.printScore();
assertThat(score).isEqualTo("30 - LOVE");
}
C’est assez rébarbatif et on se retrouve rapidement avec une classe de test qui fait plusieurs dizaines / centaines de lignes. On va voir dans cet article comment rendre nos tests plus lisibles et maintenables grâce aux tests paramétrés 😀
Cette bibliothèque va nous permettre de passer des paramètres d’entrées à nos tests. Ces paramètres proviendront d’une source de données (une méthode, un CSV, un tableau etc..).
Pour marquer un test comme un test paramétré, on va utiliser l’annotation @ParametrizedTest.
Exemple:
@ParameterizedTest
void shouldGetGameScore(int timesPlayerOneScores, int timesPlayerTwoScores, String expectedGameScore) {
Ensuite, on doit décrire d’où proviennent les données d’entrées (notre data-provider) avec une autre annotation.
Avec @MethodSource j’indique que mes données viennent de la méthode nommée “gameProvider”.
Exemple:
@ParameterizedTest
@MethodSource("gameProvider")
void shouldGetGameScore(int timesPlayerOneScores, int timesPlayerTwoScores, String expectedGameScore) {
Un test unitaire sera exécuté par chaque argument. Par exemple, pour le premier argument Arguments.of(0, 0, « LOVE – LOVE »), mon test unitaire sera lancé avec:
– Le premier paramètre (timesPlayerOneScores) qui vaut 0
– Le second paramètre (timesPlayerTwoScores) qui vaut 0 également
– Le dernier paramètre (expectedGameScore) qui a pour valeur “LOVE – LOVE”
Note: D’autres façons de transmettre de la donnée existent. On peut lire un CSV, une énum ou même donner un tableau d’entrée.
Exemple concret du cas d’usage décrit en introduction
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
class TennisTest {
private Game game;
@ParameterizedTest
@MethodSource("gameProvider")
void shouldGetGameScore(int timesPlayerOneScores, int timesPlayerTwoScores, String expectedGameScore) {
game = new Game();
playerOneScores(timesPlayerOneScores);
playerTwoScores(timesPlayerTwoScores);
String score = game.printScore();
assertThat(score).isEqualTo(expectedGameScore);
}
private void playerOneScores(int timesPlayerOneScores) {
for (int i = 0; i < timesPlayerOneScores; i++) {
game.playerOneScoresAPoint();
}
}
private void playerTwoScores(int timesPlayerTwoScores) {
for (int i = 0; i < timesPlayerTwoScores; i++) {
game.playerTwoScoresAPoint();
}
}
public static Stream<Arguments> gameProvider() {
return Stream.of(
Arguments.of(0, 0, "LOVE - LOVE"),
Arguments.of(1, 0, "15 - LOVE"),
Arguments.of(2, 0, "30 - LOVE"),
Arguments.of(3, 0, "40 - LOVE"),
Arguments.of(0, 1, "LOVE - 15"),
Arguments.of(2, 3, "30 - 40"),
Arguments.of(3, 3, "DEUCE")
);
}
}
En lançant mes tests, on a bien le détail de l’exécution:
Conclusion
Si vous avez la sensation d’écrire plusieurs fois des tests similaires, un test paramétrés pourra peut-être vous permettre de gagner en temps d’écriture et en lisibilité. C’est un outil que j’utilise au quotidien et j’espère que je vous aurais convaincu de son intérêt 😀
Dans une application Spring, sauf exceptions, les Beans sont proxifiés. En d’autres mots, on n'interagit pas directement avec eux et ce mécanisme est à la base de l’AOP.
Beaucoup de mécanismes s'appuient sur l’AOP (les Transactions par exemple) et connaître la façon dont sont proxifiés les beans est important pour prédire et comprendre certains comportements.
La documentation S...
Prérequis
Il faut que vous ayez déjà installé un JDK. Exécutez la commande:
java --version
Si vous avez bien un output de ce type avec cette commande, vous pouvez continuer.
Dans le cas contraire, il faudra installer un JDK.
...
Introduction
Il est très commun d’utiliser les transactions Spring dans nos projets, il est donc important de savoir quand celles-ci altéreront notre base de données.
On va voir via des exemples ce qui déclenche un rollback et comment customiser ce...
Introduction
Sur un des projets sur lesquels j’interviens, diverses personnes ont contribué au cours des années parfois sans passation entre les équipes.
En regardant les commits, on voit qu’une équipe a mis un ORM, une autre l’a enlevé, ...