Une fonction de calcul de remise se comporte bien pendant six mois. Un jour, un collègue ajoute un cas particulier, et le calcul se met à mal traiter les paniers de plus de cent euros.
Un test unitaire est la ligne de code qui aurait signalé la régression dans la seconde.
Définition
Un test unitaire vérifie le comportement d'une unité de code, en général une fonction, coupée de ses dépendances : pas de réseau, pas de base de données, pas d'horloge réelle. Il donne une entrée, appelle, et compare la sortie à ce qui était attendu.
import { expect, it } from "vitest";
import { remise } from "./panier.js";
it("retire 10 euros au-dessus de 100", () => {
const total = 150; // préparer
const resultat = remise(total); // agir
expect(resultat).toBe(140); // vérifier
});Ces trois temps, préparer, agir, vérifier, sont la structure de presque tous les tests unitaires. Un test qui ne les laisse pas voir est presque toujours un test qui en fait trop.
Sa place parmi les autres tests
| Type | Ce qu'il couvre | Ce qu'il coûte |
|---|---|---|
| Unitaire | Une fonction, isolée | Quelques millisecondes |
| Intégration | Plusieurs pièces réunies | Des centaines de millisecondes |
| Bout en bout | Le parcours complet dans un navigateur | Plusieurs secondes |
Le tableau tient en trois colonnes parce qu'il n'y a que trois questions : quoi, à quel prix, et donc en quelle quantité. Les tests unitaires sont les plus nombreux parce qu'ils sont les moins chers.
Ce qui rend une fonction testable
- Une valeur rendue plutôt qu'un Effet de bord : ce qui se vérifie avec un simple return se teste en une ligne.
- Des dépendances reçues en argument plutôt qu'importées en dur, ce qui permet de passer un Mock (simulacre) au moment du test.
- Aucune donnée cachée : une Fonction pure rend toujours la même chose pour les mêmes entrées, et c'est exactement ce qu'un test sait affirmer.
Quand un test devient pénible à écrire, le problème est rarement le test. C'est le signe que la fonction fait deux choses, ou qu'elle va chercher elle-même ce dont elle a besoin.
Questions fréquentes
Combien de tests pour une fonction ?
Un par comportement, pas un par ligne. En pratique cela donne le cas nominal, les bornes, et le cas d'échec attendu. Trois tests qui décrivent trois situations différentes valent mieux que dix qui répètent la même avec d'autres chiffres.
Faut-il tester les fonctions internes ?
Non, testez ce qui est exporté. Une fonction interne est un détail d'implémentation, et la tester revient à figer ce détail : la moindre réorganisation casse alors des tests sans qu'aucun comportement n'ait changé. Si une fonction interne mérite ses propres tests, elle mérite sans doute son propre module.
Écrire le test avant le code ?
C'est le principe du développement piloté par les tests, et il a un mérite indépendant du dogme : écrire l'appel avant l'implémentation oblige à décider de la signature du point de vue de celui qui l'utilise. Beaucoup d'équipes l'appliquent sur les fonctions de calcul et s'en dispensent ailleurs.