Glossaire · Fondamentaux du test
Test unitaire
En anglais : Unit test
Également appelé : tests unitaires, unit tests, unit testing, TU
Définition
Test automatisé qui vérifie un petit morceau de code, comme une fonction ou une classe, de façon isolée des bases de données, du réseau et des autres services.
Un test unitaire (unit test) exerce une petite unité de code, généralement une fonction, une méthode ou une classe, et vérifie sa sortie ou son comportement pour des entrées données. Il s’exécute en mémoire, sans vraie base de données, appel HTTP ni système de fichiers : il prend donc quelques millisecondes et échoue pour une seule raison précise. Les tests unitaires forment la base de la pyramide des tests et sont généralement écrits par les développeurs en même temps que le code.
Exemple (JUnit 5, Java 21)
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
class DiscountCalculatorTest {
private final DiscountCalculator calculator = new DiscountCalculator();
@ParameterizedTest
@CsvSource({ "99.99, 0.00", "100.00, 10.00", "250.00, 25.00" })
void appliesTenPercentFromOneHundred(double total, double expected) {
assertEquals(expected, calculator.discountFor(total), 0.001);
}
@Test
void rejectsNegativeTotals() {
assertThrows(IllegalArgumentException.class, () -> calculator.discountFor(-1));
}
}
Les tests paramétrés nécessitent l’artefact junit-jupiter-params, inclus dans la dépendance agrégée junit-jupiter. (Du vrai code manipulant de l’argent devrait utiliser BigDecimal plutôt que double ; l’exemple reste volontairement simple.)
Ce qui fait un bon test unitaire
- Rapide et déterministe. Pas de sleeps, d’horloge, d’aléatoire ni de réseau. Injectez une
Clockfixe au lieu d’appelerLocalDate.now(). - Isolé. Les dépendances sont remplacées par des doublures de test lorsqu’elles rendraient le test lent ou imprévisible.
- Un comportement par test, avec un nom qui l’énonce, pour qu’un échec s’explique de lui-même.
- Il teste le comportement, pas l’implémentation. Vérifiez les sorties et les effets observables, pas les méthodes privées, pour qu’un refactoring ne casse pas les tests.
Test unitaire ou test d’intégration
Un test unitaire vérifie votre logique en remplaçant tout le reste. Un test d’intégration vérifie que de vrais composants fonctionnent ensemble : votre repository avec une vraie base de données, votre client avec un vrai service (ou un service conteneurisé). Les tests unitaires détectent les erreurs de logique à moindre coût ; les tests d’intégration détectent les erreurs de câblage, de configuration, de sérialisation et de requêtes que les tests unitaires font disparaître sous les mocks. Une suite composée uniquement de tests unitaires peut être entièrement au vert alors que l’application ne démarre même pas.
Comment définir l’« unité »
Il n’y a pas de réponse universelle. Certaines équipes considèrent une seule classe comme l’unité et mockent tous ses collaborateurs. D’autres considèrent comme unité un petit groupe de classes qui implémentent un comportement, et ne remplacent que ce qui franchit une frontière de processus. Ce second style tend à produire des tests moins fragiles face aux refactorings.