Utiliser la couverture du code pour les tests unitaires

Important

Cet article explique la création de l’exemple de projet. Si vous avez déjà un projet, vous pouvez passer à la section Outils de couverture du code.

Les tests unitaires permettent de garantir la fonctionnalité et de fournir un moyen de vérification pour les efforts de refactorisation. La couverture du code est une mesure de la quantité de code exécutée par des tests unitaires : lignes, branches ou méthodes. Par exemple, si vous disposez d’une application simple avec seulement deux branches conditionnelles de code (branche a et branche b), un test unitaire qui vérifie qu’une branche conditionnelle a signale la couverture du code de branche de 50 %.

Cet article décrit l’utilisation de la couverture du code pour les tests unitaires avec Coverlet et la génération de rapports à l’aide de ReportGenerator. Bien que cet article se concentre sur C# et xUnit comme framework de test, MSTest et NUnit fonctionnent également. Coverlet est un projet open source sur GitHub qui fournit une infrastructure de couverture du code multiplateforme pour C#. Coverlet fait partie de la fondation .NET. Coverlet collecte les données d’exécution des tests de couverture Cobertura, qui sont utilisées pour la génération de rapports.

En outre, cet article explique comment utiliser les informations de couverture du code collectées à partir d’une série de tests Coverlet pour générer un rapport. La génération de rapports est possible à l’aide d’un autre projet open source sur GitHub - ReportGenerator. ReportGenerator convertit les rapports de couverture générés par Cobertura entre autres, en rapports lisibles par l’homme dans différents formats.

Cet article est basé sur l’exemple de projet de code source, disponible dans le navigateur d’exemples.

Système testé

Le « système testé » fait référence au code sur lequel vous écrivez des tests unitaires, il peut s’agir d’un objet, d’un service ou de tout autre élément qui expose des fonctionnalités testables. Pour cet article, vous allez créer une bibliothèque de classes qui sera le système sous test et deux projets de test unitaire correspondants.

Créer une bibliothèque de classes

À partir d’une invite de commandes dans un nouveau répertoire UnitTestingCodeCoverage, créez une bibliothèque de classes .NET standard à l’aide de la commande dotnet new classlib :

dotnet new classlib -n Numbers

L’extrait de code ci-dessous définit une classe PrimeService simple qui fournit des fonctionnalités pour vérifier si un nombre est premier. Copiez l’extrait de code ci-dessous et remplacez le contenu du fichier Class1.cs créé automatiquement dans le répertoire Numbers. Renommez le fichier Class1.cs en PrimeService.cs.

namespace System.Numbers
{
    public class PrimeService
    {
        public bool IsPrime(int candidate)
        {
            if (candidate < 2)
            {
                return false;
            }

            for (int divisor = 2; divisor <= Math.Sqrt(candidate); ++divisor)
            {
                if (candidate % divisor == 0)
                {
                    return false;
                }
            }
            return true;
        }
    }
}

Conseil

Il convient de mentionner que la bibliothèque de classes Numbers a été intentionnellement ajoutée à l’espace de noms System. Cela permet à System.Math d’être accessible sans déclaration d’espace de noms using System;. Pour plus d’informations, consultez l’espace de noms (référence C#).

Créer des projets de test

Créez deux nouveaux modèles de projet de test xUnit (.NET Core) à partir de la même invite de commandes à l’aide de la commande dotnet new xunit :

dotnet new xunit -n XUnit.Coverlet.Collector
dotnet new xunit -n XUnit.Coverlet.MSBuild

Les deux projets de test xUnit nouvellement créés doivent ajouter une référence de projet de la bibliothèque de classes Numbers. Cela permet aux projets de test d’accéder à PrimeService pour les tests. Dans l’invite de commandes, utilisez la commande dotnet add :

dotnet add XUnit.Coverlet.Collector\XUnit.Coverlet.Collector.csproj reference Numbers\Numbers.csproj
dotnet add XUnit.Coverlet.MSBuild\XUnit.Coverlet.MSBuild.csproj reference Numbers\Numbers.csproj

Le projet MSBuild est nommé de manière appropriée, car il dépend du package NuGet coverlet.msbuild. Ajoutez cette dépendance de package en exécutant la commande dotnet add package :

cd XUnit.Coverlet.MSBuild && dotnet add package coverlet.msbuild && cd ..

La commande précédente a modifié les répertoires pour définir efficacement l’étendue du projet de test MSBuild, puis ajouté le package NuGet. Lorsque cela a été fait, il a ensuite changé de répertoire, en faisant monter un niveau.

Ouvrez les deux fichiers UnitTest1.cs et remplacez leur contenu par l’extrait de code suivant. Renommez les fichiers UnitTest1.cs en PrimeServiceTests.cs.

using System.Numbers;
using Xunit;

namespace XUnit.Coverlet
{
    public class PrimeServiceTests
    {
        readonly PrimeService _primeService;

        public PrimeServiceTests() => _primeService = new PrimeService();

        [Theory]
        [InlineData(-1), InlineData(0), InlineData(1)]
        public void IsPrime_ValuesLessThan2_ReturnFalse(int value) =>
            Assert.False(_primeService.IsPrime(value), $"{value} should not be prime");

        [Theory]
        [InlineData(2), InlineData(3), InlineData(5), InlineData(7)]
        public void IsPrime_PrimesLessThan10_ReturnTrue(int value) =>
            Assert.True(_primeService.IsPrime(value), $"{value} should be prime");

        [Theory]
        [InlineData(4), InlineData(6), InlineData(8), InlineData(9)]
        public void IsPrime_NonPrimesLessThan10_ReturnFalse(int value) =>
            Assert.False(_primeService.IsPrime(value), $"{value} should not be prime");
    }
}

Créer une solution

À partir de l’invite de commandes, créez une solution pour encapsuler la bibliothèque de classes et les deux projets de test. Utilisation de la commande dotnet sln :

dotnet new sln -n XUnit.Coverage

Cela crée un nom de fichier de solution XUnit.Coverage dans le répertoire UnitTestingCodeCoverage. Ajoutez les projets à la racine de la solution.

dotnet sln XUnit.Coverage.sln add **/*.csproj --in-root

Compiler l’exemple de solution à l’aide de la commande dotnet build :

dotnet build

Si la build réussit, vous avez créé les trois projets, les projets et packages référencés de manière appropriée et mis à jour correctement le code source. Bravo !

Outils de couverture du code

Il existe deux types d’outils de couverture de code :

  • DataCollectors : DataCollectors surveille l’exécution des tests et collecte des informations sur les exécutions de test. Ils signalent les informations collectées dans différents formats de sortie, tels que XML et JSON. Pour plus d’informations, consultez votre premier DataCollector.
  • Générateurs de rapports : utilisez les données collectées à partir de séries de tests pour générer des rapports, souvent sous forme de code HTML de style.

Dans cette section, l’accent est mis sur les outils du collecteur de données.

.NET inclut un collecteur de données de couverture du code intégré, qui est également disponible dans Visual Studio. Ce collecteur de données génère un fichier .coverage binaire qui peut être utilisé pour générer des rapports dans Visual Studio. Le fichier binaire n’est pas lisible par l’homme et doit être converti en format lisible avant de pouvoir être utilisé pour générer des rapports en dehors de Visual Studio.

Conseil

L’outil dotnet-coverage est un outil multiplateforme qui peut être utilisé pour convertir le fichier binaire des résultats de test de couverture dans un format lisible par l’homme. Pour plus d’informations, consultez dotnet-coverage.

Coverlet est une alternative open source au collecteur intégré. Il génère des résultats de test en tant que fichiers XML Cobertura lisibles par l’homme, qui peuvent ensuite être utilisés pour générer des rapports HTML. Pour utiliser Coverlet pour la couverture du code, un projet de test unitaire doit avoir les dépendances de package appropriées ou s’appuyer sur les outils globaux .NET et le package NuGet coverlet.console correspondant.

Intégrer à un test .NET

Le modèle de projet de test xUnit s’intègre déjà à coverlet.collector par défaut. À partir de l’invite de commandes, remplacez les répertoires par le projet XUnit.Coverlet.Collector, puis exécutez la commande dotnet test :

cd XUnit.Coverlet.Collector && dotnet test --collect:"XPlat Code Coverage"

Notes

L’argument "XPlat Code Coverage" est un nom convivial qui correspond aux collecteurs de données de Coverlet. Ce nom est obligatoire, mais ne respecte pas la casse. Pour utiliser le collecteur de données de couverture du code intégré de .NET, utilisez "Code Coverage".

Dans le cadre de l’exécution du dotnet test, un fichier coverage.cobertura.xml est généré dans le répertoire TestResults. Le fichier XML contient les résultats. Il s’agit d’une option multiplateforme qui s’appuie sur l’interface de ligne de commande .NET, et elle est idéale pour les systèmes de génération où MSBuild n’est pas disponible.

Voici l’exemple de fichier coverage.cobertura.xml.

<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="1" branch-rate="1" version="1.9" timestamp="1592248008"
          lines-covered="12" lines-valid="12" branches-covered="6" branches-valid="6">
  <sources>
    <source>C:\</source>
  </sources>
  <packages>
    <package name="Numbers" line-rate="1" branch-rate="1" complexity="6">
      <classes>
        <class name="Numbers.PrimeService" line-rate="1" branch-rate="1" complexity="6"
               filename="Numbers\PrimeService.cs">
          <methods>
            <method name="IsPrime" signature="(System.Int32)" line-rate="1"
                    branch-rate="1" complexity="6">
              <lines>
                <line number="8" hits="11" branch="False" />
                <line number="9" hits="11" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="7" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="10" hits="3" branch="False" />
                <line number="11" hits="3" branch="False" />
                <line number="14" hits="22" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="57" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="15" hits="7" branch="False" />
                <line number="16" hits="7" branch="True" condition-coverage="100% (2/2)">
                  <conditions>
                    <condition number="27" type="jump" coverage="100%" />
                  </conditions>
                </line>
                <line number="17" hits="4" branch="False" />
                <line number="18" hits="4" branch="False" />
                <line number="20" hits="3" branch="False" />
                <line number="21" hits="4" branch="False" />
                <line number="23" hits="11" branch="False" />
              </lines>
            </method>
          </methods>
          <lines>
            <line number="8" hits="11" branch="False" />
            <line number="9" hits="11" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="7" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="10" hits="3" branch="False" />
            <line number="11" hits="3" branch="False" />
            <line number="14" hits="22" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="57" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="15" hits="7" branch="False" />
            <line number="16" hits="7" branch="True" condition-coverage="100% (2/2)">
              <conditions>
                <condition number="27" type="jump" coverage="100%" />
              </conditions>
            </line>
            <line number="17" hits="4" branch="False" />
            <line number="18" hits="4" branch="False" />
            <line number="20" hits="3" branch="False" />
            <line number="21" hits="4" branch="False" />
            <line number="23" hits="11" branch="False" />
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>

Conseil

En guise d’alternative, vous pouvez utiliser le package MSBuild si votre système de build utilise déjà MSBuild. À partir de l’invite de commandes, remplacez les répertoires par le projet XUnit.Coverlet.MSBuild, puis exécutez la commande dotnet test :

dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura

Le fichier coverage.cobertura.xml obtenu est produit en sortie. Vous pouvez suivre le guide d’intégration MSBuild ici.

Génération de rapports

Maintenant que vous pouvez collecter des données à partir d’exécutions de tests unitaires, vous pouvez générer des rapports à l’aide de ReportGenerator. Pour installer le package NuGet ReportGenerator en tant qu’outil global .NET, utilisez la commande dotnet tool install :

dotnet tool install -g dotnet-reportgenerator-globaltool

Exécutez l’outil et fournissez les options souhaitées, en fonction du fichier output coverage.cobertura.xml de l’exécution de test précédente.

reportgenerator
-reports:"Path\To\TestProject\TestResults\{guid}\coverage.cobertura.xml"
-targetdir:"coveragereport"
-reporttypes:Html

Après avoir exécuté cette commande, un fichier HTML représente le rapport généré.

Unit test-generated report

Voir aussi

Étapes suivantes