Unlocking the Power of Software Testing in ASP.NET: Enhancing Software Quality: xUnit & Coverlet — II

Belarmino Silva
5 min readAug 12, 2023

--

Tests and Test Coverage with xUnit and Coverlet — Part II

Software tests are crucial to guarantee the delivery of a good quality product, indentify bugs, additionally offer comfort in the maintenance phase, code refactoring or even when changing team members, in addition to other benefits listed in part 1.

As mentioned in the part 1, we will create a project with its corresponding test project using xUnit and Coverlet. To bring the example to life, we will create a folder for the projects. Within the folder, we will have a class library and the test project.

mkdir xUnitCoverlet
cd xUnitCoverlet

Code to be tested

In the class library below, we will have a class that contains a method that takes a string as input and validates whether it is a number and if it has no more than 10 characters.

Commands to create the class library project.

dotnet new classlib -o xUnitCoverletLib
cd xUnitCoverletLib
dotnet new gitignore
cd ..
using System.Text.RegularExpressions;

namespace xUnitCoverletLib;
public class Numbers
{
public static bool IsValid(String number)
{
if (!IsNumber(number))
{
return false;
}
if (number.Trim().Length > 10)
{
return false;
}
return true;
}
public static bool IsNumber(string number)
{
Regex regex = new(@"^[0-9]+$", RegexOptions.None, TimeSpan.FromMilliseconds(5));
return regex.IsMatch(number);
}
}

Test with xUnit

Next, we have the test project that uses xUnit, and upon creation, it already includes the Coverlet library. To confirm this, after creating the test project, if we open the file xUnitCoverletTest.csproj, we will find one of the PackageReference entries as coverlet.collector.

Commands to create the test project.

dotnet new xunit -o xUnitCoverletTest
cd xUnitCoverletTest
dotnet new gitignore
cd ..

The test class validates the result of the method execution. In our example, if the method receives a string with only numbers and no more than 10 characters, it should return true, otherwise, the result is false.

Two approaches were used for testing, one that allows passing multiple inputs as parameters to test ([Theory]) and another without parameters ([Fact]).

using xUnitCoverletLib;

namespace xUnitCoverletTest;
public class NumbersTests
{
[Fact]
public void ValidOneNumber()
{
// ARRANGE
string number = "123324324";
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.True(isNumer);
}
public static readonly object[][] InsertValidNumbers = {
new object[] { "123456782"},
new object[] { "1"},
new object[] { "233"},
};
[Theory, MemberData(nameof(InsertValidNumbers))]
public void ValidMultipleNumber(string number)
{
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.True(isNumer);
}

public static readonly object[][] InsertInvalidNumbers = {
new object[] { "12345678912"},
new object[] { "Ola"},
new object[] { "So Sabi"},
new object[] { "12345q6789"},
};
[Theory, MemberData(nameof(InsertInvalidNumbers))]
public void InvalidNumbers(string number)
{
// ACT
bool isNumer = Numbers.IsValid(number);
// ASSERT
Assert.False(isNumer);
}
}

The test project must reference our class library to be able to test the methods available in the class library. The following command allows referencing the class library. To confirm if it’s correct, you can open the file xUnitCoverletTest.csproj and check if there is a ProjectReference tag.

cd xUnitCoverletTest
dotnet add reference ../xUnitCoverletLib

With the two projects created, we can create a solution and add both to the solution , for organization purposes, follow the instructions below and an image of how it should be recognized by Visual Studio.

dotnet new sln --name xUnitCoverlet
dotnet sln add xUnitCoverletLib
dotnet sln add xUnitCoverletTest

Now we are ready to test our class library. To do this, run the command below.

cd xUnitCoverletTest
dotnet test

The result was eight successful tests and zero failed tests. You might wonder how that’s possible with only three methods, and it doesn’t seem to make sense?

This happens because each line in the array passed as a parameter in the methods with [Theory] annotations counts as a separate test.

Test coverage with Coverlet

After running the tests, we can measure the test coverage level of our example. To collect data regarding the coverage with Coverlet, we need to add the following parameter to the test command.

dotnet test --collect:"XPlat Code Coverage"

The result of executing the command creates a file named coverage.cobertura.xml, which can be found in a folder with a name that is a UUID. In our example, the folder was named fe0cc9d8–90b1–4e59–9cdd-032d8fb423a8. Each execution of the command, a new folder is generated. All the generated folders are located inside the TestResults folder, which can be found in the root of the test project.

The visual interpretation of the coverage.cobertura.xml file doesn’t seem to be practical. To better visualize the collected data, we have the ReportGenerator project, which allows converting the generated file (coverage.cobertura.xml) into a report that is easy to read and understand. You can install ReportGenerator by following the instructions below.

dotnet tool install -g dotnet-reportgenerator-globaltool

The tool allows generating reports in various formats. The following command generates reports in HTML format.

Note: The value “fe0cc9d8–90b1–4e59–9cdd-032d8fb423a8” in the following line should be changed according to the generated folder or the folder for which you want to generate the report.

reportgenerator "-reports:TestResults\fe0cc9d8-90b1-4e59-9cdd-032d8fb423a8\coverage.cobertura.xml" "-targetdir:coveragereport" -reporttypes:Html

In this example, we achieved 100% test coverage. We have an aggregated view and a view that shows the tested classes, as indicated in the following images.

The project can be found in the repository.

Tests and Test Coverage with xUnit and Coverlet — Part 1

Conclusion

Any changes made to the class library that alter the expected results will fail the tests, ensuring the consistency of the software’s behavior, for example, during the maintenance process.

It’s not always feasible to achieve 100% coverage; in such cases, the tool indicates the coverage level for each class, and we can also identify which methods need to covered. Achieving 100% coverage in a real project can be a ambitious goal, so it is essential to determine what is critical to test and should have a minimum percentage to accept level of coverage.

--

--

Belarmino Silva

I work as a Software Developer. I love the Java, ASP.NET ecosystem, and Data Engineering.