Nuke: Code coverage with Coverlet and ReportGenerator

Somebody who likes to code
Search for a command to run...

Somebody who likes to code
Nuke is an open source, cross-platform build automation solution for .NET projects. Nuke prides itself on its simplicity and extensibility that makes build automation approachable for everyone.
This post is the official continuation of the post, Nuke: Deploy ASP. NET Web App to Azure (try to check it before and download the initial code from here). Here we'll see how easy it's to run Nuke inside a GitHub Action. We'll create two workflows, ...
Google processes tens of thousands of code changes every day across a codebase of hundreds of millions of lines. The secret isn't magic tooling — it's a documented, principled approach to code review

Amazon Simple Notification Service (SNS) looks straightforward on the surface — publish a message, deliver it to subscribers — but the gap between a topic that "works in dev" and one that is productio

Amazon Simple Queue Service (SQS) is deceptively simple to get started with, but surprisingly easy to misconfigure in ways that cause silent message loss, runaway costs, duplicate processing storms, o

HTTP API is the right default choice for the majority of .NET serverless workloads. At 70% less cost than REST API, lower latency, and native JWT authorization, it covers most production requirements

AWS Lambda has become a cornerstone of modern serverless architectures, but writing a function that "just works" is very different from writing one that is performant, cost-effective, secure, and read

Unit tests help to ensure functionality and provide a means of verification for refactoring efforts. Code coverage is a measurement of the amount of code that is run by unit tests - either lines, branches, or methods.
In this post we will see how to add Nuke targets to run code coverage with Coverlet and report generation using ReportGenerator.
Coverlet is a cross platform code coverage framework for .NET, with support for line, branch and method coverage. It works with .NET Framework on Windows and .NET Core on all supported platforms.
ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov or lcov into human readable reports in various formats.
If this is the first time you hear about Nuke, please check out our previous posts Nuke: Deploy ASP. NET Web App to Azure and Nuke: Deploy Helm package locally (special guest, GitVersion).
We will use as a starting code the solution located here, clone or download the code. Let's start by adding a new project where we will write the code to test:
dotnet new classlib -n nuke-sandbox-lib
dotnet sln add nuke-sandbox-lib
Add a BasicArithmeticOperations.cs file with the following content:
public class BasicArithmeticOperations
{
public decimal Addition(decimal a, decimal b)
{
return a + b;
}
public decimal Subtraction(decimal a, decimal b)
{
return a - b;
}
public decimal Multiplication(decimal a, decimal b)
{
return a * b;
}
public decimal Division(decimal a, decimal b)
{
if (b == 0)
{
throw new InvalidOperationException();
}
return a / b;
}
}
Then, time to add the test project:
dotnet new mstest -n nuke-sandbox-test
dotnet sln add nuke-sandbox-test
Add a BasicArithmeticOperationsTests.cs file with the following content:
[TestClass]
public class BasicArithmeticOperationsTests
{
[TestMethod]
public void adding_1_plus_1_should_be_2()
{
var sut = new BasicArithmeticOperations();
var result = sut.Addition(1, 1);
Assert.AreEqual(2, result);
}
[TestMethod]
public void subtracting_1_minus_1_should_be_0()
{
var sut = new BasicArithmeticOperations();
var result = sut.Subtraction(1, 1);
Assert.AreEqual(0, result);
}
[TestMethod]
public void dividing_4_by_2_should_be_2()
{
var sut = new BasicArithmeticOperations();
var result = sut.Division(4, 2);
Assert.AreEqual(2, result);
}
}
Run the following commands to add the Coverlet and ReportGenerator Nuget packages to the Nuke project:
nuke :add-package coverlet.console --version 3.1.2
nuke :add-package ReportGenerator --version 5.1.9
Go to the _build project and add the following using statements to the Build.cs file:
using static Nuke.Common.Tools.Coverlet.CoverletTasks;
using static Nuke.Common.Tools.ReportGenerator.ReportGeneratorTasks;
using Nuke.Common.Tools.Coverlet;
using Nuke.Common.Tools.ReportGenerator;
And the following variables:
AbsolutePath CoverageDirectory => RootDirectory / "coverage";
AbsolutePath ReportDirectory => RootDirectory / "report";
Finally, the new targets:
Target Test => _ => _
.DependsOn(Compile)
.Executes(() =>
{
EnsureCleanDirectory(CoverageDirectory);
var path = RootDirectory / "nuke-sandbox-tests" / "bin" / Configuration / "net6.0" / "nuke-sandbox-tests.dll";
Coverlet(s => s
.SetTarget("dotnet")
.SetTargetArgs("test --no-build --no-restore")
.SetAssembly(path)
.SetThreshold(75)
.SetOutput(CoverageDirectory / "opencover.xml")
.SetFormat(CoverletOutputFormat.opencover));
});
Target Report => _ => _
.DependsOn(Test)
.AssuredAfterFailure()
.Executes(() =>
{
EnsureCleanDirectory(ReportDirectory);
ReportGenerator(s => s
.SetTargetDirectory(ReportDirectory)
.SetFramework("net6.0")
.SetReportTypes(new ReportTypes[] { ReportTypes.Html })
.SetReports(CoverageDirectory / "opencover.xml"));
});
We need to notice that the Test target depends on the Compile target, which will compile the solution and, therefore, the test project. Let's review the setup used in the Test target (you can see all the options here):
SetTarget: Path to the test runner application.SetTargetArgs: Arguments to be passed to the test runner.SetAssembly: Path to the test assembly.SetOutput: Output of the generated coverage report.SetFormat: Format of the generated coverage report.SetThreshold: Exits with error if the coverage % is below value.Now, let's see the Reportgenerator setup (you can see all the options here):
SetTargetDirectory: The directory where the generated report should be saved.SetReportTypes: The output formats.SetReports: The coverage reports that should be parsed.Run the nuke Report command to see the following output:
╬══════════════════════
║ Warnings & Errors
╬═════════════
[ERR] Test: Target Test has thrown an exception
═══════════════════════════════════════
Target Status Duration
───────────────────────────────────────
Restore Succeeded < 1sec
Compile Succeeded < 1sec
Test Failed 0:02 // ProcessException: Process 'dotnet.exe' exited with code 2.
Report Succeeded < 1sec
───────────────────────────────────────
Total 0:04
═══════════════════════════════════════
Build failed on 8/13/2022 7:21:51 PM. (╯°□°)╯︵ ┻━┻
Here we can see that the Test target failed, but the Report target was executed without problems. That is because the Report target has the AssuredAfterFailure() setup. Go to the report folder and open the index.html file:

As we can see, the coverage threshold was not met because the line coverage and branch coverage are under 75%. Let's define the types of coverage used by Coverlet:
In the following image, we can see the detail of the code covered by our test:
Let's add a new test to archive the minimum coverage threshold:
[TestMethod]
public void dividing_4_by_0_should_throw_an_exception()
{
var sut = new BasicArithmeticOperations();
Assert.ThrowsException<InvalidOperationException>(() => sut.Division(4, 0));
}
Run the nuke Report:
═══════════════════════════════════════
Target Status Duration
───────────────────────────────────────
Restore Succeeded 0:01
Compile Succeeded 0:02
Test Succeeded 0:02
Report Succeeded < 1sec
───────────────────────────────────────
Total 0:06
═══════════════════════════════════════
Build succeeded on 8/13/2022 7:50:42 PM. \(^ᴗ^)/
You can see all the code here. Thanks, and happy coding.