02 - Coverage : le piège du 100%
Ce que tu vas apprendre
- Ce que la couverture de code mesure réellement
- Pourquoi 100% est un objectif trompeur
- Le problème des tests sans assertions
- Un objectif de coverage raisonnable par zone
Prerequisites
Savoir écrire des tests avec bun test. Voir l'article sur les tests unitaires avec Bun.
Le mythe du 100%
J'ai bosse avec un lead dev qui exigeait 100% de coverage sur chaque merge request. Zero exception. Le résultat ? L'équipe passait plus de temps a écrire des tests inutiles pour couvrir des getters et des fichiers de config qu'a tester la vraie logique métier. On avait 100% de couverture et des bugs en prod chaque semaine.
Le coverage mesure combien de lignes de ton code sont exécutées pendant les tests. Pas combien de lignes sont correctement testees. La différence est énorme.
Ce que le coverage mesure
bun test --coverage produit un rapport avec quatre metriques :
File | % Stmts | % Branch | % Funcs | % Lines |
---------------|---------|----------|---------|---------|
order.ts | 87.5 | 75.0 | 100.0 | 87.5 |
user.ts | 92.3 | 80.0 | 90.0 | 92.3 |
- Statements : pourcentage d'instructions exécutées
- Branches : pourcentage de branches if/else/switch parcourues
- Functions : pourcentage de fonctions appelees
- Lines : pourcentage de lignes exécutées
Le branch coverage est le plus utile des quatre. Si tu as un if/else et que tes tests ne passent que dans le if, le branch coverage le signale. Le line coverage ne dirait rien si les deux branches sont sur la meme ligne.
Le problème du test sans assertion
Regarde ce test :
typescriptit("processes order", async () => {
const order = createOrder({ items: [{ price: 10, qty: 1 }] });
await processOrder(order);
});
Ce test exécuté processOrder. Le coverage monte. Mais il ne vérifié rien. L'ordre pourrait retourner n'importe quoi, jeter une erreur silencieuse, corrompre la base de donnees. Le test passerait quand meme.
C'est le "assert-free test". Le code tourne, le coverage augmente, la confiance est fausse. J'ai vu ca chez un de mes juniors sur paltemps.fr : 15 tests sans un seul expect. Quand je lui ai demande pourquoi, il m'a dit "tu m'as dit d'augmenter le coverage". Techniquement, il avait raison. Mais ses tests ne detectaient aucun bug.
Ce que le coverage ne mesure pas
Meme avec de vraies assertions, le coverage a des angles morts :
Les cas limites. Ton test passe un email valide, le coverage dit que la ligne de validation est couverte. Mais est-ce que tu as teste un email avec des caractères speciaux ? Un email vide ? Un email de 500 caractères ? La ligne est couverte, pas le comportement.
La qualité de la gestion d'erreur. Le catch est couvert ? Super. Mais est-ce qu'il log le bon message, retourne le bon code HTTP, ne leak pas de donnees sensibles ?
La concurrence. Deux requêtes simultanees sur la meme ressource. Le coverage ne teste jamais ca.
La performance. Une fonction couverte a 100% peut prendre 30 secondes sur un tableau de 10000 éléments. Le coverage ne dira rien. Voir la serie tests de performance pour ca.
Un objectif raisonnable
Voici ce que j'applique sur mes projets, paltemps.fr inclus :
| Zone du code | Objectif de coverage |
|---|---|
| Domaine / logique métier | 80%+ |
| Routes / controllers | 70% |
| Code global (moyenne) | 60% |
| Config, types, boilerplate | 0% (ne pas tester) |
Les zones a ne PAS tester :
- Le code auto-généré (Prisma client, types OpenAPI, etc.)
- Les fichiers de configuration (
docker-compose.yml,bunfig.toml) - Les définitions de types TypeScript (ils n'existent pas au runtime)
- Les wrappers d'une ligne qui delegent a une librairie
Pour exclure des fichiers du rapport, configure le bunfig.toml :
toml[test]
coverage = true
coverageSkipTestFiles = true
Coverage en CI : le garde-fou
Un seuil de coverage en CI empeche la couverture de chuter silencieusement. Pas pour atteindre un objectif arbitraire, mais pour détecter une merge request qui ajoute 500 lignes de code sans un seul test :
typescript// Dans le pipeline GitLab (voir article 00)
// Le job echoue si la coverage globale descend sous 60%
Le seuil doit etre realiste. Si ton projet est a 45% aujourd'hui, ne mets pas un seuil a 80%. Mets 40%, et monte progressivement. L'objectif c'est de ne jamais reculer, pas de sauter a un chiffre magique.
Les rapports HTML
bun test --coverage affiche un résumé dans le terminal. Pour un rapport HTML navigable, tu peux utiliser c8 ou Istanbul :
bashbunx c8 --reporter=html bun test
Ca généré un dossier coverage/ avec des pages HTML ou tu vois exactement quelles lignes sont couvertes (vert) et lesquelles ne le sont pas (rouge). En CI, sauvegarde ce dossier en artifact pour le consulter dans GitLab (voir l'article sur la CI).
Mon avis
Le coverage est un outil de détection, pas un objectif en soi. Comme un compteur de vitesse : il te dit a quelle vitesse tu vas, il ne te dit pas si tu vas dans la bonne direction.
Si tes tests attrapent les bugs avant la prod, que ta coverage soit a 60% ou 95% c'est pareil. A l'inverse, 100% de coverage avec des tests sans assertions c'est du theatre.
Regarde le coverage pour reperer les zones non testees. Demande-toi si ces zones meritent des tests. Souvent la réponse est oui pour la logique métier et non pour le boilerplate. Fais tes choix en connaissance de cause, pas pour satisfaire un chiffre.
Article précédent : 01 - Quelle stratégie de test
Article suivant : 03 - Flaky tests : le cancer du pipeline