Usare il code coverage per testing unità

Importante

Questo articolo illustra la creazione del progetto di esempio. Se si ha già un progetto, è possibile passare alla sezione Strumenti di code coverage.

Gli unit test consentono di garantire la funzionalità e di fornire un mezzo di verifica per le attività di refactoring. Il code coverage è una misura della quantità di codice eseguita dagli unit test, ovvero righe, rami o metodi. Ad esempio, se si dispone di una semplice applicazione con solo due rami condizionali di codice (ramo a e ramo b), uno unit test che verifica il ramo a genererà un report del code coverage di ramo del 50%.

Questo articolo illustra l'utilizzo del code coverage per unit test con Coverlet e generazione di report tramite ReportGenerator. Anche se questo articolo è incentrato su C# e xUnit come framework di test, funzionano anche MSTest e NUnit. Coverlet è un progetto open source in GitHub che fornisce un framework di code coverage multipiattaforma per C#. Coverlet fa parte di .NET Foundation. Coverlet raccoglie i dati di esecuzione dei test di copertura di Cobertura, che vengono usati per la generazione di report.

Questo articolo illustra anche come usare le informazioni di code coverage raccolte da un'esecuzione di test Coverlet per generare un report. La generazione di report è possibile usando un altro progetto open source in GitHub - ReportGenerator. ReportGenerator converte i report di copertura generati da Cobertura tra molti altri, in report leggibili in diversi formati.

Questo articolo si basa sul progetto di codice sorgente di esempio, disponibile nel browser esempi.

Sistema sottoposto a test

Il "sistema sottoposto a test" si riferisce al codice in cui si scrivono unit test. Potrebbe trattarsi di un oggetto, di un servizio o di qualsiasi altro elemento che espone funzionalità testabili. Per questo articolo si creerà una libreria di classi che sarà il sistema sottoposto a test e due progetti di unit test corrispondenti.

Creare una libreria di classi

Da un prompt dei comandi in una nuova directory denominata UnitTestingCodeCoverage, creare una nuova libreria di classi standard .NET usando il comando dotnet new classlib:

dotnet new classlib -n Numbers

Il frammento di codice seguente definisce una classe semplice PrimeService che fornisce funzionalità per verificare se un numero è primo. Copiare il frammento di codice seguente e sostituire il contenuto del file Class1.cs creato automaticamente nella directory Numbers. Rinominare il file Class1.cs in 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;
        }
    }
}

Suggerimento

Vale la pena ricordare che la libreria di classi Numbers è stata aggiunta intenzionalmente allo spazio dei nomi System. In questo modo System.Math è accessibile senza una dichiarazione dello spazio dei nomi using System;. Per ulteriori informazioni, consultare spazio dei nomi (Riferimenti per C#).

Creare progetti di test

Creare due nuovi modelli progetto di test xUnit (.NET Core) dallo stesso prompt dei comandi usando il comando dotnet new xunit:

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

Entrambi i progetti di test xUnit appena creati devono aggiungere un riferimento al progetto della libreria di classi Numbers. In questo modo i progetti di test hanno accesso a PrimeService per i test. Dal prompt dei comandi usare il comando 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

Il progetto MSBuild è denominato in modo appropriato, perché dipenderà dal pacchetto NuGet coverlet.msbuild. Aggiungere questa dipendenza del pacchetto eseguendo il comando dotnet add package:

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

Il comando precedente ha modificato in modo efficace la definizione dell'ambito nel progetto di test MSBuild, quindi aggiunto il pacchetto NuGet. Al termine, è stata modificata la directory, aumentando di un livello.

Aprire entrambi i file UnitTest1.cs e sostituirli con il frammento di codice seguente. Rinominare i file UnitTest1.cs in 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");
    }
}

Creazione di una soluzione

Dal prompt dei comandi creare una nuova soluzione per incapsulare la libreria di classi e i due progetti di test. Uso del comando dotnet sln:

dotnet new sln -n XUnit.Coverage

Verrà creato un nuovo nome di file di soluzione XUnit.Coverage nella directory UnitTestingCodeCoverage. Aggiungere i progetti alla radice della soluzione.

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

Compilare la soluzione usando il comando dotnet build:

dotnet build

Se la compilazione ha esito positivo, sono stati creati i tre progetti, progetti e i pacchetti sono stati referenziati in modo appropriato e il codice sorgente è stato aggiornato correttamente. le due dipendenze nell'app.

Strumento di code coverage

Esistono due tipi di strumenti di code coverage:

  • DataCollectors: DataCollectors monitora l'esecuzione dei test e raccoglie informazioni sulle esecuzioni dei test. Vengono segnalate le informazioni raccolte in vari formati di output, ad esempio XML e JSON. Per ulteriori informazioni, consultare il primo DataCollector.
  • Generatori di report: usare i dati raccolti dalle esecuzioni di test per generare report, spesso in formato HTML.

In questa sezione l'attenzione riguarda gli strumenti dell'agente di raccolta dati.

.NET include un agente di raccolta dati di code coverage predefinito, disponibile anche in Visual Studio. Questo agente di raccolta dati genera un file binario .coverage che può essere usato per generare report in Visual Studio. Il file binario non è leggibile e deve essere convertito in un formato leggibile prima che possa essere usato per generare report all'esterno di Visual Studio.

Suggerimento

Lo strumento dotnet-coverage è uno strumento multipiattaforma che può essere usato per convertire il file dei risultati dei test di copertura binaria in un formato leggibile. Per ulteriori informazioni, consultare dotnet-coverage.

Coverlet è un'alternativa open source all'agente di raccolta predefinito. Genera i risultati dei test come file XML Cobertura leggibili, che possono quindi essere usati per generare report HTML. Per usare Coverlet per il code coverage, un progetto di unit test esistente deve avere le dipendenze del pacchetto appropriate o in alternativa basarsi sugli strumenti globali .NET e sul pacchetto NuGet coverlet.console corrispondente.

Eseguire l'integrazione con il test .NET

Il modello di progetto di test xUnit si integra già con coverlet.collector per impostazione predefinita. Dal prompt dei comandi modificare le directory nel progetto XUnit.Coverlet.Collector ed eseguire il comando dotnet test:

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

Nota

L'argomento "XPlat Code Coverage" è un nome descrittivo che corrisponde agli agenti di raccolta dati di Coverlet. Questo nome è obbligatorio, ma non fa distinzione tra maiuscole e minuscole. Usare "Code Coverage" per usare l’agente di raccolta dati code coverage predefinito di .NET.

Come parte dell'esecuzione dotnet test, viene restituito un file coverage.cobertura.xml risultante nella directory TestResults. Il file XML contiene i risultati. Si tratta di un'opzione multipiattaforma che si basa sull'interfaccia della riga di comando di .NET ed è ideale per i sistemi di compilazione in cui MSBuild non è disponibile.

Di seguito è riportato il file esempio 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>

Suggerimento

In alternativa, è possibile usare il pacchetto MSBuild se il sistema di compilazione usa già MSBuild. Dal prompt dei comandi modificare le directory nel progetto XUnit.Coverlet.MSBuild ed eseguire il comando dotnet test:

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

Il file coverage.cobertura.xml risultante è l’output. È possibile seguire la guida all'integrazione di MSBuild qui

Generare report

Ora che è possibile raccogliere dati dalle esecuzioni di unit test, è possibile generare report usando ReportGenerator. Per installare il pacchetto NuGet ReportGenerator come strumento globale .NET, usare il comando dotnet tool install:

dotnet tool install -g dotnet-reportgenerator-globaltool

Eseguire lo strumento e specificare le opzioni desiderate, in base al file di out coverage.cobertura.xml file dell'esecuzione del test precedente.

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

Dopo aver eseguito questo comando, un file HTML rappresenta il report generato.

Unit test-generated report

Vedi anche

Passaggi successivi