6 min lecture16 mars 2026Testing

Pourquoi le code coverage ne suffit pas pour mesurer la qualité des tests

Le code coverage est souvent utilisé pour mesurer la qualité des tests, mais il peut donner une fausse impression de sécurité. Découvrez ses limites et les approches utilisées par les grandes entreprises pour réellement améliorer la fiabilité d'une application.

Pourquoi le code coverage ne suffit pas pour mesurer la qualité des tests

Dans beaucoup d'équipes de développement, la qualité des tests est souvent résumée à une métrique simple : le code coverage.

60 %, 80 %, 90 %, parfois même 100 %.

Sur le papier, cela paraît logique : plus une application est couverte par des tests, moins elle devrait contenir de bugs. Pourtant, l'expérience montre rapidement une réalité plus nuancée : un coverage élevé ne garantit pas la qualité des tests.

Il est tout à fait possible d'avoir un projet avec 95 % de coverage et des bugs critiques en production.

Pourquoi ? Parce que le code coverage mesure uniquement ce qui est exécuté, pas ce qui est réellement vérifié.

Dans cet article, nous allons voir :

  • pourquoi le code coverage est une métrique limitée
  • quelles sont ses principales faiblesses
  • quelles approches sont utilisées par certaines grandes entreprises technologiques
  • quelles métriques permettent réellement d'évaluer l'efficacité des tests

Ce que mesure réellement le code coverage

Le code coverage mesure la proportion du code exécutée pendant les tests.

Il existe plusieurs types de coverage.

Line coverage

Mesure le pourcentage de lignes exécutées par les tests.

Exemple :

function canDelete(user) {
  if (user.isAdmin) {
    return true
  }
  return false
}

Si un test appelle cette fonction, la ligne est considérée comme couverte, même si le résultat n'est jamais vérifié.

Branch coverage

Mesure si toutes les branches conditionnelles sont exécutées.

if (user.isAdmin) {
  allowDelete()
} else {
  denyDelete()
}

Pour atteindre 100 % de branch coverage, il faut tester :

  • un admin
  • un utilisateur normal

Cette métrique est plus utile que le simple line coverage, mais elle reste insuffisante.

Function coverage

Mesure si les fonctions sont appelées pendant les tests.

Une fonction peut donc être appelée sans que son comportement soit réellement validé.


Le problème fondamental : le coverage ne vérifie rien

Le problème fondamental du code coverage est simple : il mesure l'exécution du code, pas la validité du comportement.

Prenons un exemple.

function calculateDiscount(user) {
  if (user.isPremium) {
    return 20
  }
  return 0
}

Un test comme celui-ci peut augmenter le coverage :

calculateDiscount(user)

Mais il ne vérifie rien.

Le test devrait plutôt ressembler à ceci :

expect(calculateDiscount(premiumUser)).toBe(20)
expect(calculateDiscount(normalUser)).toBe(0)

Dans le premier cas, le coverage augmente mais aucune garantie n'existe sur le comportement réel du code.


Comment un coverage élevé peut cacher de mauvais tests

Il existe plusieurs façons d'obtenir un coverage élevé avec des tests peu utiles.

Tests trop superficiels

Certains tests exécutent simplement du code sans vérifier le résultat.

Tests couplés à l'implémentation

Des tests qui vérifient des détails internes du code plutôt que son comportement observable.

Ces tests deviennent fragiles et cassent lors des refactorings.

Tests artificiels pour augmenter le coverage

Certaines équipes écrivent des tests uniquement pour atteindre un objectif de coverage imposé par un outil ou un processus interne.

Le résultat est une suite de tests volumineuse mais peu utile.


Ce que font certaines grandes entreprises technologiques

Plusieurs entreprises reconnues ont adopté des approches différentes pour mesurer l'efficacité des tests.

Google : tester les comportements

Chez Google, l'objectif principal des tests est de valider des comportements observables.

Les tests sont organisés en trois catégories :

  • Small tests : tests unitaires isolés, rapides et déterministes
  • Medium tests : tests d'intégration entre plusieurs composants
  • Large tests : tests système ou end-to-end qui valident un workflow complet

Cette organisation permet de maintenir une pyramide de tests équilibrée :

  • beaucoup de tests unitaires
  • quelques tests d'intégration
  • peu de tests end-to-end

Ces pratiques sont détaillées dans le livre Software Engineering at Google.

Stripe : protéger les flux critiques

Stripe adopte une approche centrée sur les critical user journeys.

Chaque flux critique doit être protégé par des tests :

  • création d'un paiement
  • capture d'un paiement
  • remboursement
  • gestion d'une carte expirée

L'objectif est simple : un workflow critique ne doit pas pouvoir casser sans faire échouer un test.

Dans cette approche, le coverage global est moins important que la sécurité des scénarios clés.

Netflix : tester la résilience

Netflix va encore plus loin en testant la résistance du système aux pannes.

Pour cela, l'entreprise a développé Chaos Monkey. Cet outil provoque volontairement des incidents :

  • arrêt de serveurs
  • défaillance de services
  • perturbations réseau

Le but est de vérifier que le système continue de fonctionner malgré les défaillances.

Cette pratique est appelée chaos engineering.

Meta : tester les changements

Meta utilise une stratégie appelée diff coverage.

Le principe est simple : plutôt que mesurer la couverture de tout le projet, on vérifie que les lignes modifiées dans une pull request sont couvertes par des tests.

Cette approche concentre les efforts sur les zones les plus risquées : les changements récents.


Une métrique plus avancée : le mutation testing

Une technique beaucoup plus efficace pour évaluer la qualité des tests est le mutation testing.

Le principe :

  1. modifier volontairement le code
  2. exécuter les tests
  3. vérifier si les tests détectent l'erreur

Exemple : if (a > b) peut devenir if (a < b).

  • Si les tests échouent, la mutation est détectée.
  • Si les tests passent, cela signifie que les tests ne protègent pas réellement ce comportement.

Le résultat est un mutation score, qui mesure la capacité des tests à détecter des erreurs.


Une approche plus pragmatique

Avec l'expérience, beaucoup d'équipes adoptent une stratégie plus équilibrée.

Plutôt que viser un coverage maximal, elles cherchent à :

  • tester les scénarios critiques
  • vérifier les règles métier importantes
  • détecter rapidement les régressions
  • maintenir des tests rapides et fiables

Dans ce modèle, un projet avec 70 % de coverage mais des scénarios critiques bien testés est souvent plus robuste qu'un projet avec 95 % de coverage artificiel.


Conclusion

Le code coverage reste un outil utile pour identifier les zones peu testées d'un projet. Cependant, il ne doit pas être considéré comme une mesure directe de la qualité des tests.

Les équipes les plus expérimentées utilisent généralement une combinaison d'approches :

  • tests orientés comportement
  • protection des flux critiques
  • mutation testing
  • tests de résilience
  • diff coverage

L'objectif n'est pas simplement de couvrir du code, mais de garantir que le système reste fiable face aux changements et aux erreurs.


Aller plus loin

Dans un autre article sur mon site, je détaille également :

  • comment auditer rapidement la qualité d'une suite de tests
  • quelles métriques permettent réellement d'identifier les tests inutiles
  • comment améliorer progressivement une stratégie de tests existante

👉 L'article complet est disponible ici