Empleo de cobertura de código para pruebas unitarias

Importante

En este artículo se explica cómo crear el proyecto de ejemplo. Si ya tiene un proyecto, puede ir directamente a la sección Herramientas de cobertura de código.

Las pruebas unitarias ayudan a garantizar la funcionalidad y proporcionan una forma de comprobar los esfuerzos de refactorización. La cobertura de código es una medida de la cantidad de código que ejecutan las pruebas unitarias, ya sean líneas, ramas o métodos. Por ejemplo, si tiene una aplicación sencilla con solo dos ramas condicionales de código (rama a y rama b), una prueba unitaria que compruebe la rama a condicional notifica una cobertura de código del 50 %.

En este artículo se habla del uso de la cobertura de código para pruebas unitarias con Coverlet y de la generación de informes con ReportGenerator. Aunque este artículo se centra en C# y xUnit como marco de pruebas, MSTest y NUnit también funcionan. Coverlet es un proyecto de código abierto en GitHub que proporciona un marco de cobertura de código multiplataforma para C#. Coverlet es parte de .NET Foundation. Coverlet recopila datos de series de pruebas de cobertura de Cobertura, que se usan para la generación de informes.

Además, en este artículo se detalla cómo usar la información de cobertura de código recopilada de una serie de pruebas de Coverlet para generar un informe. La generación de informes es posible gracias a otro proyecto de código abierto en GitHub: ReportGenerator. ReportGenerator convierte los informes de cobertura generados por Cobertura, entre otros, en informes legibles para el usuario en distintos formatos.

Este artículo se basa en el proyecto de código fuente de ejemplo, disponible en el explorador de ejemplos.

Sistema sometido a prueba

El "sistema sometido a prueba" se refiere al código en el que se están escribiendo las pruebas unitarias, que podría ser un objeto, un servicio o cualquier otro elemento que exponga funcionalidad que se puede probar. En este artículo se va a crear una biblioteca de clases que va a ser el sistema sometido a prueba y dos proyectos de prueba unitaria correspondientes.

Creación de una biblioteca de clases

Desde un símbolo del sistema de un nuevo directorio denominado UnitTestingCodeCoverage, cree una nueva biblioteca de clases de .NET Standard con el comando dotnet new classlib:

dotnet new classlib -n Numbers

El siguiente fragmento de código define una clase PrimeService simple que proporciona funcionalidad para comprobar si un número es primo. Copie el fragmento de código siguiente y reemplace el contenido del archivo Class1.cs creado automáticamente en el directorio Numbers. Cambie el nombre del archivo Class1.cs a 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;
        }
    }
}

Sugerencia

Merece la pena mencionar que la biblioteca de clases Numbers se ha agregado a propósito al espacio de nombres System. Esto permite que System.Math sea accesible sin una declaración de espacio de nombres using System;. Para obtener más información, vea namespace (Referencia de C#).

Creación de proyectos de prueba

Cree dos nuevas plantillas Proyecto de pruebas xUnit (.NET Core) desde el mismo símbolo del sistema con el comando dotnet new xunit:

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

Los dos proyectos de pruebas xUnit recién creados necesitan agregar una referencia de proyecto de la biblioteca de clases Numbers, de modo que los proyectos de pruebas tengan acceso a PrimeService para las pruebas. Desde el símbolo del sistema, use el 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

El nombre del proyecto MSBuild es correcto, ya que depende del paquete NuGet coverlet.msbuild. Agregue esta dependencia de paquete mediante la ejecución del comando dotnet add package:

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

El comando anterior ha cambiado el ámbito de los directorios al proyecto de prueba MSBuild y ha agregado el paquete NuGet. Una vez hecho esto, se han cambiado los directorios, avanzando un nivel.

Abra los dos archivos UnitTest1.cs y reemplace su contenido por el siguiente fragmento de código. Cambie el nombre de los archivos UnitTest1.cs a 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");
    }
}

Crear una solución

Desde el símbolo del sistema, cree una nueva solución para encapsular la biblioteca de clases y los dos proyectos de prueba. Uso del comando dotnet sln:

dotnet new sln -n XUnit.Coverage

Se crea un nuevo nombre de archivo de solución XUnit.Coverage en el directorio UnitTestingCodeCoverage. Agregue los proyectos a la raíz de la solución.

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

Compile la solución mediante el comando dotnet build:

dotnet build

Si la compilación se realiza correctamente, ha creado los tres proyectos, ha hecho referencia adecuadamente a los proyectos y paquetes y ha actualizado el código fuente de forma correcta. ¡Bien hecho!

Herramientas de cobertura de código

Hay dos tipos de herramientas de cobertura de código:

  • Recopiladores de datos: los recopiladores de datos supervisan la ejecución de pruebas y recopilan información sobre las series de pruebas. Notifican la información recopilada en diversos formatos de salida, como XML y JSON. Para obtener más información, vea Primer recopilador de datos.
  • Generadores de pruebas: use los datos recopilados de las series de pruebas para generar informes, a menudo como HTML con estilo.

Esta sección se centra en las herramientas de recopilación de datos.

.NET incluye un recopilador de datos de cobertura de código integrado, que también está disponible en Visual Studio. Este recopilador de datos genera un archivo .coverage binario que se puede usar para generar informes en Visual Studio. El archivo binario no es legible por el usuario y debe convertirse a un formato legible para poder usarse a fin de generar informes fuera de Visual Studio.

Sugerencia

dotnet-coverage es una herramienta multiplataforma que se puede usar para convertir el archivo de resultados de pruebas de cobertura binaria a un formato legible por el usuario. Para obtener más información, vea dotnet-coverage.

Coverlet es una alternativa de código abierto al recopilador integrado. Genera resultados de prueba como archivos XML de Cobertura legibles por usuarios, que luego se pueden usar para generar informes HTML. A fin de usar Coverlet para la cobertura de código, un proyecto de prueba unitaria existente debe tener las dependencias de paquete adecuadas, o bien depender de herramientas globales de .NET y el paquete NuGet coverlet.console correspondiente.

Integración con pruebas de .NET

La plantilla de proyecto de prueba xUnit ya está integrada con coverlet.collector de forma predeterminada. Desde el símbolo del sistema, cambie los directorios al proyecto XUnit.Coverlet.Collector y ejecute el comando dotnet test:

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

Nota:

El argumento "XPlat Code Coverage" es un nombre descriptivo que corresponde a los recopiladores de datos de Coverlet. Este nombre es necesario, aunque no distingue mayúsculas de minúsculas. Para utilizar el recopilador de datos de cobertura de código integrado de .NET, use "Code Coverage".

Como parte de la ejecución de dotnet test, se genera un archivo coverage.cobertura.xml resultante en el directorio TestResults. El archivo XML contiene los resultados. Se trata de una opción multiplataforma basada en la CLI de .NET que es ideal para los sistemas de compilación cuando MSBuild no está disponible.

A continuación se muestra el archivo de ejemplo 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>

Sugerencia

Como alternativa, puede usar el paquete MSBuild si el sistema de compilación ya usa MSBuild. Desde el símbolo del sistema, cambie los directorios al proyecto XUnit.Coverlet.MSBuild y ejecute el comando dotnet test:

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

Se genera el archivo coverage.cobertura.xml resultante. Puede seguir la guía de integración de msbuild aquí.

Generación de informes

Ahora que puede recopilar datos de las series de pruebas unitarias, puede generar informes mediante ReportGenerator. Para instalar el paquete NuGet ReportGenerator como una herramienta global de .NET, use el comando dotnet tool install:

dotnet tool install -g dotnet-reportgenerator-globaltool

Ejecute la herramienta y proporcione las opciones deseadas según el archivo coverage.cobertura.xml de salida de la serie de pruebas anterior.

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

Después de ejecutar este comando, un archivo HTML representa el informe generado.

Unit test-generated report

Consulte también

Pasos siguientes