🔝 Retour au Sommaire
La couverture de code (code coverage en anglais) est une métrique essentielle pour évaluer la qualité de vos tests. Elle mesure quelle proportion de votre code source est réellement exécutée lorsque vos tests s'exécutent.
Imaginez que vous avez écrit une application avec 10 000 lignes de code et une suite de tests. Comment savoir si vos tests vérifient bien tout votre code ? C'est exactement ce que la couverture de code vous permet de découvrir.
Avantages de la couverture de code :
- Identifier le code non testé : découvrir les parties de votre application qui ne sont jamais exécutées par vos tests
- Améliorer la qualité des tests : repérer les branches conditionnelles, les cas d'erreur ou les fonctions oubliées
- Réduire les bugs : plus le code est testé, moins il y a de risques de bugs en production
- Confiance lors des modifications : savoir qu'une modification ne casse rien grâce à une bonne couverture
- Documentation vivante : la couverture montre quelles parties du code sont réellement utilisées
- 100% de couverture ≠ code sans bugs : vous pouvez exécuter chaque ligne sans vérifier que le résultat est correct
- La couverture n'est pas une fin en soi : c'est un indicateur, pas un objectif absolu
- Quantité ≠ qualité : 100 tests de mauvaise qualité valent moins que 10 excellents tests
- La couverture ne teste pas la logique métier : elle mesure seulement ce qui est exécuté, pas si c'est correct
Règle d'or : La couverture de code est un outil de diagnostic, pas un badge d'honneur. Une couverture de 80% avec de bons tests vaut mieux que 100% avec des tests superficiels.
Il existe plusieurs niveaux de couverture, du plus simple au plus complexe :
Définition : Pourcentage de lignes de code exécutées par les tests.
Exemple :
function Diviser(a, b: Integer): Integer;
begin
if b = 0 then // Ligne 1
raise Exception.Create('Division par zéro'); // Ligne 2
Result := a div b; // Ligne 3
end;Test :
// Test qui ne couvre que les lignes 1 et 3
resultat := Diviser(10, 2); // ✅ Ligne 1 exécutée, ✅ Ligne 3 exécutée
// ❌ Ligne 2 jamais exécutéeCouverture : 2/3 lignes = 66.7%
Pour atteindre 100%, il faut ajouter un test :
// Test qui couvre la ligne 2
try
Diviser(10, 0); // ✅ Ligne 1, ✅ Ligne 2 exécutées
except
// Exception attendue
end;Définition : Pourcentage de branches conditionnelles (if, case, while) testées.
Exemple :
function ClasserNote(note: Integer): string;
begin
if note >= 90 then // Branche A: vraie ou fausse ?
Result := 'Excellent'
else if note >= 70 then // Branche B: vraie ou fausse ?
Result := 'Bien'
else if note >= 50 then // Branche C: vraie ou fausse ?
Result := 'Passable'
else
Result := 'Insuffisant';
end;Pour une couverture complète des branches, il faut tester :
- note = 95 → branche A vraie ✅
- note = 75 → branche A fausse, B vraie ✅
- note = 55 → branches A et B fausses, C vraie ✅
- note = 30 → toutes branches fausses, else exécuté ✅
Minimum de tests nécessaires : 4 tests pour couvrir toutes les branches.
Définition : Pourcentage de fonctions/procédures appelées au moins une fois.
Exemple :
function Additionner(a, b: Integer): Integer;
begin
Result := a + b;
end;
function Soustraire(a, b: Integer): Integer;
begin
Result := a - b;
end;
function Multiplier(a, b: Integer): Integer;
begin
Result := a * b;
end;Test :
// Test qui appelle seulement 2 fonctions sur 3
x := Additionner(5, 3); // ✅ Fonction appelée
y := Multiplier(4, 2); // ✅ Fonction appelée
// ❌ Soustraire jamais appeléeCouverture de fonction : 2/3 = 66.7%
Définition : Teste toutes les combinaisons possibles dans les expressions booléennes complexes.
Exemple :
if (age >= 18) and (permis = True) then
PeutConduire := True
else
PeutConduire := False;Pour une couverture complète :
- Test 1: age=20, permis=True → les deux conditions vraies ✅
- Test 2: age=16, permis=True → première fausse, deuxième vraie ✅
- Test 3: age=20, permis=False → première vraie, deuxième fausse ✅
- Test 4: age=16, permis=False → les deux conditions fausses ✅
Définition : Teste tous les chemins d'exécution possibles à travers le code (la plus exhaustive et la plus difficile).
Exemple :
procedure Traiter(x, y: Integer);
begin
if x > 0 then // Point de décision 1
WriteLn('X positif');
if y > 0 then // Point de décision 2
WriteLn('Y positif');
end;Chemins possibles :
- x > 0 ET y > 0 → deux messages affichés
- x > 0 ET y ≤ 0 → premier message seulement
- x ≤ 0 ET y > 0 → deuxième message seulement
- x ≤ 0 ET y ≤ 0 → aucun message
Minimum de tests : 4 tests pour couvrir tous les chemins.
Il n'existe pas de pourcentage magique, mais voici des recommandations générales :
| Type de Projet | Couverture Recommandée | Justification |
|---|---|---|
| Bibliothèque critique | 90-100% | Code réutilisé, bugs coûteux |
| Application métier | 70-80% | Bon équilibre qualité/effort |
| Prototype/POC | 40-60% | Tests sur fonctionnalités clés |
| Code legacy | 50-70% | Amélioration progressive |
| API publique | 85-95% | Interface critique |
Formule générale :
Couverture (%) = (Éléments exécutés / Éléments totaux) × 100
Exemple concret :
Votre projet contient :
- 500 lignes de code au total
- Vos tests exécutent 380 lignes
Couverture = (380 / 500) × 100 = 76%
Couverture < 50% 🔴
- Beaucoup de code non testé
- Risque élevé de bugs
- Action : Écrire des tests pour les fonctionnalités critiques
Couverture 50-70% 🟡
- Couverture acceptable pour débuter
- Fonctionnalités principales testées
- Action : Identifier les zones critiques non couvertes
Couverture 70-85% 🟢
- Bonne couverture générale
- Confiance raisonnable
- Action : Maintenir et améliorer progressivement
Couverture > 85% 🟢🟢
- Excellente couverture
- Haute confiance
- Action : Se concentrer sur la qualité des tests existants
Commencez par tester en priorité :
- Code métier complexe : algorithmes, calculs, logique business
- Points d'entrée : API, endpoints, interfaces publiques
- Code à haut risque : gestion d'erreurs, sécurité, transactions
- Code fréquemment modifié : parties instables du code
// Tester le chemin nominal (cas normal)
procedure TestAdditionNormale;
begin
AssertEquals(5, Additionner(2, 3));
end;
// Tester les cas limites
procedure TestAdditionZero;
begin
AssertEquals(5, Additionner(5, 0));
AssertEquals(5, Additionner(0, 5));
end;
// Tester les cas d'erreur
procedure TestAdditionNegatif;
begin
AssertEquals(-1, Additionner(2, -3));
end;Utilisez les outils de profiling (détaillés dans les sections suivantes) pour générer un rapport de couverture.
Analysez le rapport et identifiez :
- Les fonctions jamais appelées
- Les branches conditionnelles non testées
- Les cas d'erreur ignorés
Intégrez la mesure de couverture dans votre processus :
- CI/CD : bloquer les PR si la couverture baisse
- Révisions de code : vérifier que les nouveaux tests couvrent le nouveau code
- Refactoring : maintenir ou améliorer la couverture
HAUTE PRIORITÉ (viser 90%+)
├── Couche métier
│ ├── Calculs financiers
│ ├── Algorithmes de traitement
│ └── Validation des données
├── API publique
│ ├── Endpoints REST
│ └── Interfaces exposées
MOYENNE PRIORITÉ (viser 70%+)
├── Services applicatifs
├── Accès aux données
└── Gestion des erreurs
BASSE PRIORITÉ (viser 40%+)
├── Code d'interface utilisateur
├── Configuration
└── Logging
FreePascal propose plusieurs approches pour mesurer la couverture de code :
| Outil/Méthode | Plateforme | Type de Couverture | Complexité |
|---|---|---|---|
| gprof | Windows/Linux | Fonction, ligne | Moyenne |
| Valgrind (Callgrind) | Linux | Ligne, branche | Moyenne |
| lcov | Linux | Ligne, fonction | Faible |
| FPCUnit + scripts | Multi-plateforme | Personnalisable | Élevée |
Quelle que soit la plateforme, le processus est similaire :
1. INSTRUMENTATION
↓
Compiler le code avec des options spéciales
qui ajoutent du code de traçage
2. EXÉCUTION
↓
Lancer les tests
Le programme enregistre quelles parties sont exécutées
3. COLLECTE
↓
Générer un fichier de données brutes
(gmon.out, callgrind.out, etc.)
4. ANALYSE
↓
Transformer les données en rapport lisible
5. VISUALISATION
↓
Afficher les résultats (texte, HTML, graphique)
Utilisez gprof si :
- ✅ Vous voulez une solution simple et rapide
- ✅ La couverture de fonction suffit
- ✅ Vous travaillez sur Windows ou Linux
Utilisez Valgrind si :
- ✅ Vous êtes sous Linux
- ✅ Vous voulez une analyse très détaillée
- ✅ Vous avez besoin de couverture de branche
Créez un script personnalisé si :
- ✅ Vous avez des besoins spécifiques
- ✅ Vous intégrez dans un pipeline CI/CD
- ✅ Vous voulez automatiser complètement le processus
Mauvaise approche :
// Test inutile juste pour augmenter la couverture
procedure TestGetterSetterEvident;
begin
personne.SetNom('Jean');
AssertEquals('Jean', personne.GetNom);
end;Bonne approche :
// Test qui vérifie la logique métier
procedure TestValidationNomPersonne;
begin
// Nom valide
AssertTrue(personne.SetNom('Jean Dupont'));
// Nom vide refusé
AssertFalse(personne.SetNom(''));
// Nom trop long refusé
AssertFalse(personne.SetNom(StringOfChar('A', 200)));
// Caractères spéciaux refusés
AssertFalse(personne.SetNom('Jean<script>'));
end;Ne testez pas que le chemin heureux :
// ❌ Incomplet : teste seulement le cas normal
procedure TestDivision;
begin
AssertEquals(5, Diviser(10, 2));
end;
// ✅ Complet : teste normal + cas limites + erreurs
procedure TestDivisionComplete;
begin
// Cas normal
AssertEquals(5, Diviser(10, 2));
// Cas limites
AssertEquals(0, Diviser(0, 5));
AssertEquals(1, Diviser(7, 7));
// Cas d'erreur
AssertException(EDivisionParZero, @DiviserParZero);
end;Certains codes ne méritent pas d'être testés :
{$IFDEF COVERAGE_EXCLUDE}
procedure LoggerDebug(msg: string);
begin
// Simple logging, pas de logique métier
WriteLn(FormatDateTime('hh:nn:ss', Now), ' - ', msg);
end;
{$ENDIF}Code à exclure généralement :
- Logging simple
- Getters/setters triviaux
- Code généré automatiquement
- Code de débogage uniquement
Workflow idéal :
DÉVELOPPEMENT
├── 1. Écrire le test (TDD)
├── 2. Implémenter le code
├── 3. Exécuter les tests
├── 4. Mesurer la couverture
└── 5. Combler les trous si nécessaire
AVANT COMMIT
├── Tests passent ✅
├── Couverture acceptable ✅
└── Code review ✅
CI/CD
├── Build automatique
├── Tests automatiques
├── Rapport de couverture
└── Blocage si couverture < seuil
Avant de refactorer du code legacy :
1. Mesurer la couverture actuelle (baseline)
2. Écrire des tests pour atteindre 70%+ sur le code à modifier
3. Refactorer en vérifiant que les tests passent toujours
4. Mesurer à nouveau : la couverture doit être maintenue ou améliorée
// Ce code est 100% couvert mais FAUX
function CalculerMoyenne(a, b: Integer): Real;
begin
Result := a + b; // ❌ Oubli de diviser par 2
end;
// Test qui passe mais ne vérifie rien d'utile
procedure TestMoyenne;
begin
resultat := CalculerMoyenne(10, 20);
AssertTrue(resultat > 0); // ✅ Passe mais ne vérifie pas que résultat = 15
end;// 100% de couverture, 0% de valeur
procedure TestBidon;
begin
FonctionComplexe(param1, param2);
// ❌ Aucune assertion ! Le test ne vérifie rien
end;La couverture mesure généralement les tests unitaires, pas :
- L'intégration entre composants
- Les tests end-to-end
- Les tests de performance
- Les tests de sécurité
Pour une évaluation complète de la qualité, combinez :
QUALITÉ DU CODE
├── Couverture de code (70%+)
├── Tests qui passent (100%)
├── Complexité cyclomatique (<10 par fonction)
├── Duplication de code (<3%)
├── Temps d'exécution des tests (<5 min)
└── Code review approuvé
La couverture de code est un outil puissant mais doit être utilisée intelligemment :
- Mesurer régulièrement la couverture
- Viser une couverture réaliste (70-85% pour la plupart des projets)
- Prioriser le code critique
- Tester les cas limites et les erreurs
- Utiliser la couverture pour identifier les zones à risque
- Viser 100% à tout prix
- Écrire des tests inutiles juste pour augmenter la couverture
- Ignorer la qualité des tests au profit de la quantité
- Négliger les tests d'intégration
- Considérer la couverture comme la seule métrique de qualité
Philosophie : La couverture de code est une boussole, pas une destination. Elle vous indique où aller, mais c'est à vous de choisir le meilleur chemin.
Dans les sections suivantes, nous verrons comment mettre en œuvre concrètement la couverture de code avec les outils disponibles sur Windows et Linux.