-
Notifications
You must be signed in to change notification settings - Fork 6.1k
New article that details unit test code coverage using coverlet #18955
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
3ae87de
Initial bits of new article
IEvangelist 44ec2b6
Fix linting issue
IEvangelist f630e32
Correct link
IEvangelist 12ffc59
Updates
IEvangelist af1999d
Final updates
IEvangelist a432599
Fix md error
IEvangelist e7e9a79
Apply suggestions from code review
IEvangelist d41c237
Many more updates
IEvangelist ae506d0
Massive updates, more like a tutorial now
IEvangelist 73dd099
Added a lot and managed updates
IEvangelist 5e77384
Minor tweaks
IEvangelist 24fed38
Updates for acrolinx
IEvangelist cfc46b5
Another minor update
IEvangelist 3cbe09a
Apply suggestions from code review
IEvangelist 6735415
Updates from peer review.
IEvangelist 486532c
Proper casing
IEvangelist 54f53fc
Final change
IEvangelist File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,301 @@ | ||
| --- | ||
| title: Use code coverage for unit testing | ||
| description: Learn how to use the code coverage capabilities for .NET unit tests. | ||
| author: IEvangelist | ||
| ms.author: dapine | ||
| ms.date: 06/16/2020 | ||
| --- | ||
|
|
||
| # Use code coverage for unit testing | ||
|
|
||
| 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. As an example, if you have a simple application with only two conditional branches of code (_branch a_, and _branch b_), a unit test that verifies conditional _branch a_ will report branch code coverage of 50%. | ||
|
|
||
| This article discusses the usage of code coverage for unit testing with Coverlet and report generation using ReportGenerator. While this article focuses on C# and xUnit as the test framework, both MSTest and NUnit would also work. Coverlet is an [open source project on GitHub](https://github.com/coverlet-coverage/coverlet) that provides a cross platform code coverage framework for C#. [Coverlet](https://dotnetfoundation.org/projects/coverlet) is part of the .NET foundation. Coverlet collects Cobertura coverage test run data, which is used for report generation. | ||
|
|
||
| Additionally, this article details how to use the code coverage information collected from a Coverlet test run to generate a report. The report generation is possible using another [open source project on GitHub - ReportGenerator](https://github.com/danielpalme/ReportGenerator). ReportGenerator converts coverage reports generated by Cobertura among many others, into human readable reports in various formats. | ||
|
|
||
| ## System under test | ||
|
|
||
| The "system under test" refers to the code that you're writing unit tests against, this could be an object, service, or anything else that exposes testable functionality. For the purpose of this article, you'll create a class library that will be the system under test, and two corresponding unit test projects. | ||
|
|
||
| ### Create a class library | ||
|
|
||
| From a command prompt in a new directory named `UnitTestingCodeCoverage`, create a new .NET standard class library using the [`dotnet new classlib`](../tools/dotnet-new.md#classlib) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet new classlib -n Numbers | ||
| ``` | ||
|
|
||
| The snippet below defines a simple `PrimeService` class that provides functionality to check if a number is prime. Copy the snippet below and replace the contents of the *Class1.cs* file that was automatically created in the *Numbers* directory. Rename the *Class1.cs* file to *PrimeService.cs*. | ||
|
|
||
| ```csharp | ||
| 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; | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > It is worth mentioning the that `Numbers` class library was intentionally added to the `System` namespace. This allows for <xref:System.Math?displayProperty=fullName> to be accessible without a `using System;` namespace declaration. For more information, see [namespace (C# Reference)](../../csharp/language-reference/keywords/namespace.md). | ||
|
|
||
| ### Create test projects | ||
|
|
||
| Create two new **xUnit Test Project (.NET Core)** templates from the same command prompt using the [`dotnet new xunit`](../tools/dotnet-new.md#test) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet new xunit -n XUnit.Coverlet.Collector | ||
| ``` | ||
|
|
||
| ```dotnetcli | ||
| dotnet new xunit -n XUnit.Coverlet.MSBuild | ||
| ``` | ||
|
|
||
| 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`](../tools/dotnet-add-reference.md) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet add XUnit.Coverlet.Collector\XUnit.Coverlet.Collector.csproj reference Numbers\Numbers.csproj | ||
| ``` | ||
|
|
||
| ```dotnetcli | ||
| 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](https://www.nuget.org/packages/coverlet.msbuild) NuGet package. Add this package dependency by running the [`dotnet add package`](../tools/dotnet-add-package.md) command: | ||
|
|
||
| ```dotnetcli | ||
| 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*. | ||
|
|
||
| ```csharp | ||
| 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`](../tools/dotnet-sln.md) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet new sln -n XUnit.Coverage | ||
| ``` | ||
|
|
||
| This will create a new solution file name `XUnit.Coverage` in the *UnitTestingCodeCoverage* directory. Add the projects to the root of the solution. | ||
|
|
||
| ## [Linux](#tab/linux) | ||
|
|
||
| ```dotnetcli | ||
| dotnet sln XUnit.Coverage.sln add **/*.csproj --in-root | ||
| ``` | ||
|
|
||
| ## [Windows](#tab/windows) | ||
|
|
||
| ```dotnetcli | ||
| dotnet sln XUnit.Coverage.sln add (ls **/*.csproj) --in-root | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| Build the solution using the [`dotnet build`](../tools/dotnet-build.md) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet build | ||
| ``` | ||
|
|
||
| If the build is successful, you've created the three projects, appropriately referenced projects and packages, and updated the source code correctly. Well done! | ||
|
|
||
| ## Tooling | ||
|
|
||
| There are two types of code coverage tools: | ||
|
|
||
| - **DataCollectors:** DataCollectors monitor test execution and collect information about test runs. They report the collected information in various output formats, such as XML and JSON. For more information, see [your first DataCollector](https://github.com/Microsoft/vstest-docs/blob/master/docs/extensions/datacollector.md). | ||
| - **Report generators:** Use data collected from test runs to generate reports, often as styled HTML. | ||
|
|
||
| In this section, the focus is on data collector tools. To use Coverlet for code coverage, an existing unit test project must have the appropriate package dependencies, or alternatively rely on [.NET global tooling](../tools/global-tools.md) and the corresponding [coverlet.console](https://www.nuget.org/packages/coverlet.console) NuGet package. | ||
|
|
||
| ## Integrate with .NET test | ||
|
|
||
| The xUnit test project template already integrates with [coverlet.collector](https://www.nuget.org/packages/coverlet.collector) by default. | ||
| From the command prompt, change directories to the *XUnit.Coverlet.Collector* project, and run the [`dotnet test`](../tools/dotnet-test.md) command: | ||
|
|
||
| ```dotnetcli | ||
| cd XUnit.Coverlet.Collector && dotnet test --collect:"XPlat Code Coverage" | ||
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > The `"XPlat Code Coverage"` argument is a friendly name that corresponds to the data collectors from Coverlet. This name is required but is case insensitive. | ||
|
|
||
| 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 Core CLI, and it is great for build systems where MSBuild is not available. | ||
|
|
||
| Below is the example *coverage.cobertura.xml* file. | ||
|
|
||
| ```xml | ||
| <?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> | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > 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: | ||
| > | ||
| > ```dotnetcli | ||
| > dotnet test --collect:"XPlat Code Coverage" | ||
| > ``` | ||
| > | ||
| > The resulting *coverage.cobertura.xml* file is output. | ||
|
|
||
| ## Generate reports | ||
|
|
||
| Now that you're able to collect data from unit test runs, you can generate reports using [ReportGenerator](https://github.com/danielpalme/ReportGenerator). To install the [ReportGenerator](https://www.nuget.org/packages/dotnet-reportgenerator-globaltool) NuGet package as a [.NET global tool](../tools/global-tools.md), use the [`dotnet tool install`](../tools/dotnet-tool-install.md) command: | ||
|
|
||
| ```dotnetcli | ||
| dotnet tool install -g dotnet-reportgenerator-globaltool | ||
| ``` | ||
|
|
||
| Run the tool and provide the desired options, given the output *coverage.cobertura.xml* file from the previous test run. | ||
IEvangelist marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```console | ||
| reportgenerator | ||
| "-reports:Path\To\TestProject\TestResults\{guid}\coverage.cobertura.xml" | ||
| "-targetdir:coveragereport" | ||
| -reporttypes:Html | ||
| ``` | ||
|
|
||
| After running this command, an HTML file represents the generated report. | ||
|
|
||
| :::image type="content" source="media/test-report.png" lightbox="media/test-report.png" alt-text="Unit test-generated report"::: | ||
|
|
||
| ## See also | ||
|
|
||
| - [Visual Studio unit test cover coverage](/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested) | ||
| - [GitHub - Coverlet repository](https://github.com/coverlet-coverage/coverlet) | ||
| - [GitHub - ReportGenerator repository](https://github.com/danielpalme/ReportGenerator) | ||
| - [ReportGenerator project site](https://danielpalme.github.io/ReportGenerator) | ||
| - [.NET Core CLI test command](../tools/dotnet-test.md) | ||
|
|
||
| ## Next Steps | ||
|
|
||
| > [!div class="nextstepaction"] | ||
| > [Unit testing best practices](unit-testing-best-practices.md) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.