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: