Unit Test (4-1) --- Code Coverage Report

Note: this article is published on 08/24/2024.

This series of articles will discuss Unit Test

This article follows the previous one, Unit Test (4) --- Code Coverage, and further get the code coverage reporing.

A - Introduction


The previous article, has described the basic of code coverage. This article is in fact a further discussion about the code coverage report. Due to the main matierials of this article are from the article

where the discussion is based on a command line app. For myself, because I have never had experience to build a .Net Project from command line, So I just followed the article to go through the process, as a practice. So, this part is written as a learning note. You can skip this part, Part B, to Part C directly.

This article includes:

  • A - Introduction
  • B - Setup and Build the Test Environment through Command Line
    • Create a class library
    • Create test projects
    • Create a solution
  • C - Visual Studio with Code Coverage
    • Integrate with .NET test
    • Generate reports

B - Setup and Build the Test Environment through Command Line


This part is simple to setup and build a Unit Test Project. If you do have a or any Unit Test Project, or even you can download the sample code from here, you may skip this Section.

From a command prompt in a new directory named UnitTestingCodeCoverage, create a new .NET standard class library using the dotnet new classlib command:

Create a class library

dotnet new classlib -n Numbers

Folder created:

Files created:

Rename the Class1.cs file to PrimeService.cs. and fill in the code:

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;
        }
    }
}

Create test projects

Create two new xUnit Test Project (.NET Core) templates from the same command prompt using the dotnet new xunit command:

dotnet new xunit -n XUnit.Coverlet.Collector

Folder created:

Files created:

Do the same to another project:

dotnet new xunit -n XUnit.Coverlet.MSBuild

Folder created:

Files created:

Both of the newly created xUnit test projects need to add a project reference of the Numbers class library. This is so that the test projects have access to the PrimeService for testing. From the command prompt, use the dotnet add command:

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

The MSBuild project is named appropriately, as it will depend on the coverlet.msbuild NuGet package. Add this package dependency by running the dotnet add package command:

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

The previous command changed directories effectively scoping to the MSBuild test project, then added the NuGet package. When that was done, it then changed directories, stepping up one level.

Open both of the UnitTest1.cs files, and replace their contents with the following snippet. Rename the UnitTest1.cs files to 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");
    }
}

Create a solution

From the command prompt, create a new solution to encapsulate the class library and the two test projects. Using the dotnet sln command:

dotnet new sln -n XUnit.Coverage

solution file created:

This will create a new solution file name XUnit.Coverage in the UnitTestingCodeCoverage directory. Add the projects to the root of the solution.

The following command does not work, for either Windows (if it is):

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

or linux:

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

I just skipped, and add the projects into the solution by Visual Studio.

Build the solution using the dotnet build command:

dotnet build

Build successful:

C - Code Coverage Report Generator


This is the point we got from the previous article, Unit Test (4) --- Code Coverage,

now we continue.

Integrate with .NET test

The xUnit test project template already integrates with coverlet.collector by default. From the command prompt, change directories to the XUnit.Coverlet.Collector project, and run the dotnet test command:

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

As part of the dotnet test run, a resulting coverage.cobertura.xml file is output to the TestResults directory. The XML file contains the results. This is a cross-platform option that relies on the .NET CLI, and it is great for build systems where MSBuild is not available.

Below is the example coverage.cobertura.xml file.

<?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>

As an alternative, you could use the MSBuild package if your build system already makes use of MSBuild. From the command prompt, change directories to the XUnit.Coverlet.MSBuild project, and run the dotnet test command:

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

we got the same result, the .xml file:

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<coverage line-rate="1" branch-rate="1" version="1.9" timestamp="1724359676" 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="System.Numbers.PrimeService" filename="Users\gghan\source\repos\Work\1. Prudential\Annuities Archive\Unit Test\UnitTestingCodeCoverage\Numbers\PrimeService.cs" line-rate="1" branch-rate="1" complexity="6">
<methods>
<method name="IsPrime" signature="(System.Int32)" line-rate="1" branch-rate="1" complexity="6">
<lines>
<line number="6" hits="11" branch="False"/>
<line number="7" hits="11" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="7" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="8" hits="3" branch="False"/>
<line number="9" hits="3" branch="False"/>
<line number="12" hits="22" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="57" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="13" hits="7" branch="False"/>
<line number="14" hits="7" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="27" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="15" hits="4" branch="False"/>
<line number="16" hits="4" branch="False"/>
<line number="18" hits="3" branch="False"/>
<line number="19" hits="4" branch="False"/>
<line number="20" hits="11" branch="False"/>
</lines>
</method>
</methods>
<lines>
<line number="6" hits="11" branch="False"/>
<line number="7" hits="11" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="7" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="8" hits="3" branch="False"/>
<line number="9" hits="3" branch="False"/>
<line number="12" hits="22" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="57" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="13" hits="7" branch="False"/>
<line number="14" hits="7" branch="True" condition-coverage="100% (2/2)">
<conditions>
<condition number="27" type="jump" coverage="100%"/>
</conditions>
</line>
<line number="15" hits="4" branch="False"/>
<line number="16" hits="4" branch="False"/>
<line number="18" hits="3" branch="False"/>
<line number="19" hits="4" branch="False"/>
<line number="20" hits="11" branch="False"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

Generate reports

Now that you're able to collect data from unit test runs, you can generate reports using ReportGenerator. To install the ReportGenerator NuGet package as a .NET global tool, use the dotnet tool install command:

dotnet tool install -g dotnet-reportgenerator-globaltool

The ReportGenerator is successfully installed:

Run the command reportgenerator:

The command works, but it needs the inputs. Now we run the command with input files:

reportgenerator -reports:"coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html

we need to move the command in one line, and with appropriate folder location:

A folder named coveragereport is created:

that is a html page files:

The report:

 

References:


Similar Articles