Beginner's Guide to Test Coverage with NUnit, Coverlet, and ReportGenerator
Tags: csharp, dotnet, beginners, webdev
Published: 2024-06-17 12:57:08

Hi guys, today I am going to show a quick and simple way to get started with Test Coverage in your code. 
## What is Test Coverage?
Test coverage is the percentage of code that is tested by automated tests. That is, it measures the extent to which a codebase is **covered** by tests we write.

Prerequisites for this tutorial;
Basic knowledge of C# and Unit testing.

Requirements;
1. Visual Studio - Powerful IDE for .NET and C++ development on Windows.
2. NUnit - Unit Testing framework for .NET. More [here](https://github.com/nunit/nunit)
3. Coverlet - Cross-platform code coverage framework for .NET. More [here](https://github.com/coverlet-coverage/coverlet)
4. ReportGenerator - Powerful code coverage visualization tool. More [here](https://github.com/danielpalme/ReportGenerator)

Let's set-up our test application.

Create a new console Application using .NET Framework

![Create console app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2guglcp6prddpuqlul7c.png)

Give your project a name **TestCoverageDemo**. Then select .NET framework 4.7.2 as the target framework and click **Create**.

Great, now In your application add a simple class called `Calculator`  and add the following code

```cs
public class Calculator
{
    public static int Add(int number)
    {
        int sum = 20;
        return sum + number;
    }
}
```

This class contains a simple implementation that takes a number, adds 20 to it, and returns the result. Seems simple enough.

Write a unit test for the implementation.

Create a new project by right-clicking on your solution file and selecting Add -> New project

Search NUnit

![Configure Nunit project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jtr7wyza2q72ab152spb.png)

You can leave the default NUnit project name of **TestProject1**. Click next. Select .NET Framework 4.7.2 as the target framework and click Create.

After Creating, your project should look like this.

![Project Structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5l1ukvsx2rlfcdwy21ws.png)

Next, add a reference to the main `TestCoverageDemo` project from the test project

Now, Add the following code to the Class `UnitTest1`

```cs
    public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Test1()
        {
            // Arrange
            int number = 2;
            int expected = 22;

            // Act
            int actual = Calculator.Add(number);

            // Assert
            Assert.That(actual, Is.EqualTo(expected));
        }
    }
```

Your class should look something like this;

![Class structure](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3pi7z3bh12j9yhw2f7e.png)

Now, right-click on the Test name and select **Run Tests** 

After running the test, we see that we get a passing test;

![Passing test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n62czokd5gxbtlwiztie.png)

Next, Install **Coverlet** by navigating to the command line of your test project and running this command;

```
dotnet add package coverlet.collector
```

Next, create a file in the directory of the test project, and call it `coverlet.runsettings`, and add this piece of code:

```cs
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat Code Coverage">
        <Configuration>
          <ExcludeByFile>**/Program.cs</ExcludeByFile>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>
```

We're simply adding this so the program.cs does not get in the way of our demo.

Then, while still in the the Test directory, run this command;

```
dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings
```

This will run your tests and generate a `coverage.cobertura` xml file in the **TestResults** folder

This file is hard to read, so we need the ReportGenerator tool to visualize it. 

To Install ReportGenerator, run this command;
```
dotnet tool install -g dotnet-reportgenerator-globaltool
```

Next, run this command;

```
reportgenerator -reports:"TestCoverageDemo\TestProject1\TestResults\e36a31dc-6f0e-410a-a860-cc32118ec3a8\coverage.cobertura.xml" -targetdir:"coverageresults" -reporttypes:Html
```

This command will go into the location of where the **coverage.cobertura** file is located, parse that file and present the result as a viewable Html site in the target directory we specified: `coverageresults`  

Navigating to the Test project folder, we see that the folder `coverageresults` has been generated

![Coverage folder generated](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ruz81vviwg5nbdm4wj5.png)

Navigate into the `coverageresults` folder and click on **index**,  the coverage results have been presented in a nice way and we have 100% coverage;

![Coverage results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30ma6icol3ukxwcp6lw2.png)

Scrolling down, we will see a better breakdown of what is covered

![Coverage Breakdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cle3v26psi9q2w6anrzz.png)

Clicking on TestCoverageDemo.Calculator we see that we have 100% of coverage and that all the lines of code we've written are covered by the unit test. 

![Line Coverage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rwd1akttmdbdrl2uy1vk.png)

Great.

Let's make some modifications to the Add method in the calculator class, by adding this code:

```cs
 public static int Add(int number)
 {
     int sum;

     if(number % 2 == 0)
     {
         sum = 20;
     }
     else
     {
         sum = 30;
     }
     return sum + number;
 }
```

Now, in your test explorer run the test `Test1` again

We see that the test still passes as expected.

![Test passes after changes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v4vdxpttk7kes5ky2hg8.png)

Now, Let's try to recalculate the test coverage once more. Run these 2 commands in the directory of the test project again,

```
dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings
```

```
reportgenerator -reports:"TestCoverageDemo\TestProject1\TestResults\e36a31dc-6f0e-410a-a860-cc32118ec3a8\coverage.cobertura.xml" -targetdir:"coverageresults" -reporttypes:Html
```

Now, navigate to the coverageresults folder once more and click the index.

![Coverage has reduced](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/diqkwkhxs572vox7zm07.png)

We see that the coverage has dropped from 100% to 70%

If we investigate further by checking TestCoverageDemo.Calculator class, we can see why;

![Breakdown of coverage after changes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8q0mwvp2pvcj4o6rjbzf.png)

The `else`   block is not covered by the test because we have not created any scenario where that part of the code is tested.

Let's fix that.

Add this piece of code to the UnitTest1 class;
```cs
 public void Test2()
 {
     // Arrange
     int number = 3;
     int expected = 33;

     // Act
     int actual = Calculator.Add(number);

     // Assert
     Assert.That(actual, Is.EqualTo(expected));
 }
```

Run the test and see that it passes.

Now run the commands to generate the test coverage once more.

Navigate to coverageresult and click **Index** once more.

![Coverage after changing tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0baq6viljdo5rcfvts6u.png)

We see that the test coverage is back to 100%. 

Checking TestCoverageDemo.Calculator will reveal a further breakdown;

![Breakdown of test coverage by line](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xuoelwa1t9jgh334uebp.png)

And we see that the new code we introduced to the system is now covered by the tests as well. 

This was a very brief introduction to test coverage in C# applications we have gone through. You can check out some of the tools we used in this tutorial and use them in your projects as well.

Happy coding