📖 Manuel
Test Coverage Analyzer
Workflow
1. Choisir et configurer l'outil de coverage
JavaScript / TypeScript
# c8 (natif V8, recommandé Node 18+)
npm install --save-dev c8
npx c8 --reporter=lcov --reporter=text npm test
# Istanbul / nyc (legacy mais encore répandu)
npx nyc --reporter=lcov --reporter=text mocha
.NET / C#
dotnet add package coverlet.collector
dotnet test --collect:"XPlat Code Coverage" \
--results-directory ./TestResults
# Convertir en HTML (nécessite reportgenerator)
reportgenerator -reports:TestResults/**/coverage.cobertura.xml \
-targetdir:coverage-html -reporttypes:Html
Python
pip install pytest-cov
pytest --cov=src --cov-report=html --cov-report=term-missing
Java (Maven)
<!-- pom.xml -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution><goals><goal>prepare-agent</goal></goals></execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
mvn test jacoco:report # rapport dans target/site/jacoco/
2. Interpréter les métriques — critères de décision
| Métrique | Signification | Seuil minimum recommandé |
|---|---|---|
| Line coverage | Lignes exécutées / total | 80 % global |
| Branch coverage | Chaque if/else/switch couverts | 70 % |
| Function coverage | Fonctions appelées ≥ 1 fois | 90 % |
| Statement coverage | Instructions individuelles | ≈ line coverage |
| Mutation score | Tests détectent les mutations | 70 % |
Priorité : branch > line. Un fichier à 90 % line coverage peut n'avoir aucun test de sa branche catch ou de son chemin null.
3. Identifier les zones non couvertes (priorité)
Ordre d'analyse :
- Fonctions/méthodes à zéro couverture dans le code métier critique (paiement, auth, calculs financiers).
- Branches de gestion d'erreur (
catch,else,default) jamais exercées. - Fichiers entiers non couverts (oubli d'import dans le test runner).
- Lignes isolées non couvertes dans des fonctions par ailleurs testées (early returns, guards).
Détecter le dead code réel : une ligne non couverte depuis des mois n'est pas forcément du dead code — vérifier via git log ou grep si elle est appelée en production. Utiliser knip (TS) ou vulture (Python) pour détecter le dead code structurel.
# TypeScript : dead exports
npx knip
# Python : dead code
pip install vulture
vulture src/ --min-confidence 80
4. Prioriser les efforts de test
Règle 80/20 : concentrer les efforts sur le code à impact business, pas sur maximiser le chiffre.
À couvrir en priorité :
- Logique métier (validation, calcul, transformation de données)
- Chemins d'erreur et cas limites (valeur nulle, liste vide, token expiré)
- Intégrations externes (clients HTTP, accès BDD) via mocks
À ne pas couvrir ou ignorer explicitement :
- Code généré automatiquement (migrations EF Core, scaffolding, proto-generated)
- Fichiers de configuration pure (
.config.ts,appsettings.jsonwrappers) - Bootstrap / entry points (
main.ts,Program.cs) sauf si logique métier dedans
.NET — exclure du coverage :
[ExcludeFromCodeCoverage]
public class GeneratedMappingProfile : Profile { ... }
Jest / c8 — exclure via commentaire :
/* c8 ignore next 3 */
if (process.env.NODE_ENV === 'development') { ... }
Python — exclure via .coveragerc :
[report]
omit =
*/migrations/*
*/generated/*
*/manage.py
5. Générer des tests ciblés pour les gaps
Pour chaque zone non couverte, utiliser ce pattern :
- Lire le code source de la zone manquante.
- Identifier le contrat de la fonction (entrées, sorties, effets de bord).
- Écrire un test qui suit le chemin non couvert (branch spécifique).
- Re-lancer le rapport et vérifier que la couverture progresse.
Exemple — branche catch non couverte :
// Code source
async function fetchUser(id: string) {
try {
return await db.findUser(id);
} catch (e) {
logger.error(e); // ← jamais testé
throw new AppError('USER_NOT_FOUND');
}
}
// Test ajouté
it('rethrows AppError when db throws', async () => {
jest.spyOn(db, 'findUser').mockRejectedValue(new Error('db down'));
await expect(fetchUser('1')).rejects.toThrow('USER_NOT_FOUND');
});
6. Mutation testing — valider la qualité des assertions
La couverture de lignes ne garantit pas que les tests assertent correctement. Le mutation testing modifie le code source (change > en >=, supprime un return, etc.) et vérifie que les tests échouent.
Stryker (.NET / JS/TS)
# .NET
dotnet tool install -g dotnet-stryker
dotnet stryker
# JavaScript/TypeScript
npx stryker run
mutmut (Python)
pip install mutmut
mutmut run
mutmut results
mutmut show <id> # voir le mutant survivant
PITest (Java)
mvn org.pitest:pitest-maven:mutationCoverage
# rapport dans target/pit-reports/
Interprétation :
- Score < 50 % : assertions trop faibles, tests passent même avec du code cassé.
- Score 50–70 % : acceptable, améliorer les assertions des cas limites.
- Score > 70 % : bonne qualité de test.
- Score 100 % : probablement trop lent en CI, échantillonner par module.
7. Configurer les seuils CI/CD
GitHub Actions — Jest / c8
- name: Test with coverage
run: npx c8 --lines 80 --branches 70 --functions 90 npm test
# Retourne exit code 1 si seuil non atteint
.NET — coverlet avec seuil bloquant
dotnet test /p:CollectCoverage=true \
/p:CoverletOutputFormat=cobertura \
/p:Threshold=80 \
/p:ThresholdType=line
Python — pytest-cov avec fail-under
pytest --cov=src --cov-fail-under=80
Diff coverage (ne pas régresser sur le nouveau code) :
# diff-cover compare le coverage du diff courant vs main
pip install diff-cover
coverage xml
diff-cover coverage.xml --compare-branch=origin/main --fail-under=90
8. Visualisation et suivi dans le temps
- Codecov : intégration GitHub/GitLab, badge de couverture, commentaires PR automatiques.
- Coveralls : alternative légère pour projets open source.
- SonarQube / SonarCloud : coverage + code smells + duplication dans un seul dashboard.
- ReportGenerator (.NET) : rapport HTML local multi-projets avec tendances.
Configuration Codecov minimale (.codecov.yml) :
coverage:
status:
project:
default:
target: 80%
threshold: 2% # tolérance de baisse acceptable
patch:
default:
target: 90% # nouveau code doit être bien couvert
Garde-fous / Anti-patterns / Pièges
- Chasing 100 % : ajouter des tests sans assertion (
expect(true).toBe(true)) pour gonfler la couverture. Résultat : couverture haute, qualité nulle. → Toujours coupler avec mutation testing. - Ignorer la branch coverage : line coverage à 95 % mais les branches
catchetelsejamais exercées. → Configurer--branchesen CI obligatoire. - Tests couplés à l'implémentation : tester l'état interne plutôt que le comportement observable. Les refactorings cassent tous les tests. → Tester les contrats publics.
- Seuils trop bas au départ : configurer 30 % en CI parce que le projet est en retard. → Difficile à remonter ensuite. Mieux vaut démarrer à 60 % et incrémenter.
- Coverage sur le code de test lui-même : certains outils incluent les fichiers
*.spec.tsdans le rapport. → Exclure explicitement via glob. - Oublier l'exclusion du code généré : les migrations ou proto-generated files faussent les chiffres vers le bas. → Configurer les exclusions dès le départ.
- Mutation testing en CI sur tout le projet : trop lent. → Lancer en CI sur les fichiers modifiés uniquement, full run en nightly build.