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 😀
Avant .Net 8, tester du code qui utilise DateTime.Now() n’était pas trivial, on devait faire en sorte de mocker la Clock dans nos tests. Depuis .Net 8, c’est beaucoup plus facile grâce à TimeProvider inclut par défaut !
En deux mots, TimeProvider est une classe abstraite et on va pouvoir injecter dans nos tests une implémentation “Fake” qui va nous permettre de changer la date actu...
La gestion des erreurs de son API est très importante pour que les consommateurs puissent avoir une description claire du problème mais c’est souvent fastidieux à maintenir.
On va voir comment avoir des statuts de réponse cohérent et des messages d’erre...
On va voir comment avoir en quelques minutes des assertions qui vont vérifier les endpoints de notre API avec des scénarios de ce genre:
Feature: Create a new account
As a visitor,
I can create an account to access the game
Scenario: A visitor c...
Depuis .NET 9, le le support d’OpenAPI est directement inclus dans .NET et ne passe plus par les librairies Swagger par défaut (plus d’info sur ce choix ici si jamais ça vous intéresse).
De façons simplifiée, la librairie Swashbuckle.AspNetCore.Sw...