Mesurez la couverture de vos tests

Avant de parler de tests d’intĂ©gration, je voudrais vous parler un peu de couverture de code. Plus vous allez Ă©crire de tests et plus vous aurez besoin d’avoir des indicateurs sur ce qui est testĂ©. La mesure de la couverture de code est un indicateur intĂ©ressant que tout dĂ©veloppeur qui se respecte doit connaĂźtre.

Principe de la couverture de code

La couverture de code est une mesure utilisĂ©e pour dĂ©terminer le taux de code source exĂ©cutĂ© lorsqu’une suite de tests est lancĂ©e. Pour essayer de limiter les bugs, les tests doivent couvrir une large proportion de code.

Il y a plusieurs indicateurs de couverture de code :

  • Couverture des fonctions (ou mĂ©thodes) : est-ce que toutes les mĂ©thodes du code ont Ă©tĂ© appelĂ©es par les tests ?

  • Couverture des instructions : est-ce que les tests sont passĂ©s sur chaque ligne de code ?

  • Couverture des chemins d’exĂ©cution : est-ce que l’on est passĂ© dans toutes les branches de notre code ? Par exemple, l’instruction  if  gĂ©nĂšre deux branches de code : une dans laquelle la condition Ă©valuĂ©e est vraie, une autre oĂč la condition est fausse.

  • Couverture des points de tests : est-ce que chaque condition sur le test d’une variable a Ă©tĂ© couverte ?

En gĂ©nĂ©ral, la plupart des outils proposent la couverture des instructions et des chemins d’exĂ©cution. Et la mĂ©trique la plus fiable est celle des chemins d’exĂ©cution, c’est celle que vous devez privilĂ©gier.

Les différents outils de mesure

Il existe plusieurs outils permettant de mesurer la couverture de code. Nous allons passer en revue les plus connus.

  • Visual Studio possĂšde un outil de mesure de code.

Par contre, il n’est utilisable qu’à partir de l’édition professionnelle de Visual Studio, qui est payante (contrairement Ă  la version community, que j’utilise ici, qui est gratuite). Si vous avez une version payante de Visual Studio, et c’est trĂšs souvent le cas en entreprise, n’hĂ©sitez pas Ă  utiliser cette fonctionnalitĂ© de couverture de code. Vous trouverez plus d’informations ici.

  • Il existe aussi DotCover, qui est l’outil de Jetbrains.

C’est un logiciel payant Ă©galement, mais certaines entreprises achĂštent des licences de Resharper.

DotCover est inclus avec certaines licences des produits JetBrains. N’hĂ©sitez pas Ă  vĂ©rifier si vous utilisez dĂ©jĂ  un autre logiciel de JetBrains, vous aurez peut-ĂȘtre accĂšs Ă  DotCover. Vous trouverez plus d’informations ici.

  • Un autre outil assez connu est NCover.

Toujours payant, c’est un outil qui existe depuis trĂšs longtemps et que l’on retrouve parfois associĂ© Ă  d’autres logiciels. Suivez ce lien si vous souhaitez avoir plus d’informations.

  • Et je terminerai par une derniĂšre option : OpenCover.

Il s’agit d’un outil open source sous licence MIT. C’est celui que nous allons utiliser dans ce cours, sachant qu’il ne fonctionne que sous Windows et que nous allons donc installer Ă©galement des extensions pour pouvoir l’utiliser correctement. Le projet se trouve sur github, mais vous n'avez pas besoin de le cloner, il suffira de rĂ©fĂ©rencer le bon package Nuget.

Utilisez OpenCover

Pour utiliser OpenCover, il faut commencer par référencer notre package Nuget, directement dans notre projet de test :

Ajouter un package Nuget
Ajouter un package Nuget

Il faut ensuite trouver OpenCover et l’installer :

Choix du package OpenCover
Choix du package OpenCover

OpenCover est le moteur d’analyse de la couverture de code. Il nous faut maintenant une interface permettant de lire les rĂ©sultats de l’analyse. Avec Visual Studio 2017, nous pouvons installer l’extension AxoCover :

AxoCover sur le market place de Visual Studio
AxoCover sur le market place de Visual Studio

Une fois l’extension tĂ©lĂ©chargĂ©e, vous pouvez l’installer :

Installation de l'extension OpenCover
Installation de l'extension OpenCover

Une fois l’installation terminĂ©e, redĂ©marrez Visual Studio et rouvrez votre solution.

Vous avez désormais un nouvel élément dans le menu outils :

Un nouveau menu AxoCover
Un nouveau menu AxoCover

Si vous cliquez dessus, une nouvelle fenĂȘtre s'ouvre :

Nos tests dans AxoCover
Nos tests dans AxoCover

Normalement, nous y voyons nos tests.

Il ne vous reste plus qu’à cliquer sur Cover pour dĂ©marrer les tests et l’analyse.

AprĂšs l’exĂ©cution, vous pouvez constater l’apparition d’un nouvel onglet Report :

Affichez le rapport de couverture de code
Affichez le rapport de couverture de code

Cliquez dessus pour voir les résultats de la couverture de code :

Le résultat de la couverture de code
Le résultat de la couverture de code

Nous pouvons voir plein de petites choses. 🙂

  • Vous voyez notamment les noms des namespaces et des classes, ainsi que deux pourcentages Ă  leurs cĂŽtĂ©s.

Le premier correspond au pourcentage des branches qui sont couvertes et le second au pourcentage de lignes couvertes. Par exemple, nous pouvons voir que la classe  Heros  est couverte Ă  100 %, alors que la classe  Ihm  n’est couverte qu’à 87,5 % au niveau des chemins d’exĂ©cution.

  • Vous voyez aussi que les classes  De ,  FournisseurMeteo  et  ConsoleDeSortie  sont Ă  0 %.

Eh oui, lors des tests, nous avons remplacé ces dépendances aléatoires par de fausses implémentations...

Si nous cliquons sur la classe  Ihm , nous avons des prĂ©cisions supplĂ©mentaires sur l’analyse de cette classe : 

Résultats de la couverture de code
Résultats détaillés de la couverture de code

Nous voyons donc que nous avons une branche qui n’est pas couverte par nos tests et qu'elle est dans la mĂ©thode  Demarre() .

Mince ! Mais quelle est cette branche non couverte et qui va prendre froid ? 😜

Pour le savoir, il suffit de cliquer sur Source - Ă  droite - et nous voilĂ  redirigĂ©s dans le code de la classe. Mais il n’est plus tout Ă  fait comme avant. Sur la gauche, nous avons des couleurs :

Affichage de la couverture de code dans l'IDE
Affichage de la couverture de code dans l'IDE

Un trait rouge indique qu’une ligne (ou plusieurs) n’est pas couverte. Vous le voyez, c’est le cas pour le contenu de la mĂ©thode  Main . C’est bien normal, Ă©tant donnĂ© que nous n’avons aucun test qui appelle ce bout de code. Évidemment, la ligne verte indique que la ligne est couverte.

Pour les branches, c’est un petit peu plus subtil, car il s’agit d’un petit cercle sur la gauche :

Affichage de l'indicateur de couverture de branches de code
Affichage de l'indicateur de couverture de branches de code

Lorsque tout est vert, c’est que toutes les options sont couvertes. Typiquement, pour la condition de la boucle  while, il faut avoir un cas oĂč elle est vraie et un cas oĂč elle est fausse, ce qui est le cas dans nos tests.

Pour le  switch  - et c’est ce qui explique notre score de 85,7 % -, nous n’avons pas gĂ©rĂ© de cas par dĂ©faut. En fait, c’est comme si nous avions Ă©crit ce code :

Le code détaillé par branches
Le code détaillé par branche

Nous voyons bien, sur cette capture d’écran, avec le rĂ©sultat de la couverture de code, que nous ne passons jamais dans le cas  default . En effet, nous n’avons que 2 valeurs dans notre Ă©numĂ©ration, difficile de se retrouver dans le cas d’une valeur qui n’existe pas !

On pourrait considĂ©rer ici que l’outil de couverture de code nous remonte un faux positif, car dans notre code, nous avons couvert toutes les possibilitĂ©s. Mais techniquement, il pourrait ĂȘtre envisageable d’avoir d’autres options. Une  enum  n’est rien de plus qu’un entier, finalement. Le tour de jeu pourrait trĂšs bien renvoyer ceci Ă  un moment ou Ă  un autre :

public Resultat Tour(int deHeros, int deMonstre)
{
    return (Resultat)3;
}

Ce code compile et fonctionne. Mais alors, ce cas n’est pas traitĂ© par notre premiĂšre version du code. En revanche, avec la seconde version, nous aurons une exception levĂ©e pour cette valeur qui n’est pas gĂ©rĂ©e explicitement.

Donc, vous avez deux options :

  • Il faut Ă©crire un nouveau test pour gĂ©rer ce cas-lĂ .

Cela nĂ©cessiterait d’extraire une interface de la classe  Jeu , de rĂ©cupĂ©rer la dĂ©pendance dans le constructeur et d’utiliser un bouchon ou un simulacre pour renvoyer une valeur qui ne fait pas partie de l’énumĂ©ration. Et lĂ , cela nous fait pas mal de travail pour pas grand-chose


  • L’autre option est de réécrire le premier code pour avoir un  default  Ă  la place du  Resultat.Perdu  (ligne 6) :

switch (resultat)
{
    case Resultat.Gagne:
        _console.Ecrire($"Monstre battu");
        break;
    default:
        _console.Ecrire($"Combat perdu");
        break;
}

Avec ce code-là, la couverture de code repasse à 100 % pour la classe  Ihm  . Chouette ! 🙂

Couverture de code et qualité

Nous venons de voir que la mesure peut ĂȘtre diffĂ©rente en fonction de la façon dont nous avons Ă©crit notre code. Cependant, on pourrait se demander si la réécriture prĂ©sentĂ©e ci-dessus est bien utile.

En effet, nos tests couvraient avec succĂšs tous les cas fonctionnels de notre tour de jeu. Alors, faut-il nous obstiner Ă  modifier le code et passer de 85 % Ă  100 % ?

C’est lĂ  qu'il faut ĂȘtre capable de se rendre compte de ce qu’est vraiment cet indicateur de couverture de code.

Pour qu’il puisse vous ĂȘtre utile, il faut bien comprendre comment il fonctionne et ce qu’il nous dit vraiment derriĂšre les chiffres.

On pourrait penser que plus le pourcentage est élevé, plus le code est de qualité.

Qu’en pensez-vous ?

C’est la premiĂšre chose qui vient Ă  l’esprit. En consĂ©quence, il paraĂźtrait logique de vouloir atteindre 100 % de couverture de code.

Pour nous en convaincre, prenons un exemple simpliste :

[TestClass]
public class ExempleTest
{
    [TestMethod]
    public void Test1()
    {
        StringHelper.EstCeQueLaChaineEstLongue("abc");
    }
}

public static class StringHelper
{
    public static bool EstCeQueLaChaineEstLongue(string chaine)
    {
        if (chaine.Length > 10)
            return true;
        else
            return false;
    }
}

Le code parle de lui-mĂȘme, on veut savoir si une chaĂźne est longue ou pas.

Oui, je dĂ©cide arbitrairement qu’une chaĂźne est longue si elle dĂ©passe dix caractĂšres. 🙂

Notre test appelle la méthode avec une chaßne courte ; nous avons donc une couverture de code de 66,67 % au niveau des branches, et de 80 % au niveau des lignes de code :

Couverture de la classe StringHelper
Couverture de la classe StringHelper

En effet, nous ne sommes pas passĂ©s par le cas oĂč la chaĂźne est longue.

La logique voudrait que nous ajoutions un cas de test, cette fois-ci avec une chaßne longue. Mais soyons fous et changeons plutÎt le code de la méthode à tester :

public static bool EstCeQueLaChaineEstLongue(string chaine)
{
    return chaine.Length > 10;
}

Maintenant, relançons l’analyse. Nous obtenons :

Couverture Ă  100% de la classe
Couverture Ă  100 % de la classe

Nous avons cette fois-ci une couverture de code Ă  100 % . đŸ˜Č Pourtant, le code fait exactement la mĂȘme chose que prĂ©cĂ©demment ! Il nous manque toujours un cas de test, mais l’indicateur dit bien 100 %.

C’est grave, non ?

Mais il y a pire que ça ! Vous voyez ? Oui, j’espùre que vous avez vu. 🙂

En vĂ©ritĂ©, avec ce test, nous ne testons rien. Nous avons simplement dĂ©marrĂ© une mĂ©thode. Le code correct de test aurait dĂ» ĂȘtre dans ce cas, a minima :

StringHelper.EstCeQueLaChaineEstLongue("abc").Should().BeFalse();

Ici, nous vérifions un résultat, il y a une assertion.

Alors, que pouvez-vous en penser ?

Nous avons vu que l’indicateur de couverture de code Ă©tait Ă  100 %, alors que tous les scĂ©narios de tests n’existaient pas et que le test ne testait finalement rien.

Je vous l’accorde, mon exemple est extrĂȘme. Mais son objectif est de vous faire comprendre qu’un haut pourcentage de couverture de code n’est pas forcĂ©ment synonyme de qualitĂ© de test. Cet indicateur fournit une tendance, il faut l’utiliser sur la durĂ©e.

En gĂ©nĂ©ral, si l'on a peu de couverture de code, c’est un indicateur pour rajouter des tests. Au contraire, avoir une grosse couverture de code ne veut pas forcĂ©ment dire que tout est bien testĂ© et que les tests sont de qualitĂ©.

Il faut aussi rester raisonnable et pragmatique. Le Graal de 100 % de couverture de code est en gĂ©nĂ©ral impossible Ă  atteindre. Sur un vrai projet, il demande bien souvent trop d’efforts (et donc un coĂ»t trop important) par rapport Ă  l’intĂ©rĂȘt d’avoir une telle couverture. D’autant plus que nous venons de voir que ce taux ne garantit pas la qualitĂ©.

Et si vous obteniez un diplĂŽme OpenClassrooms ?
  • Formations jusqu’à 100 % financĂ©es
  • Date de dĂ©but flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous