Going all the way back to .NET Core 2.1, Microsoft has provided us with the
dotnet test command as a base for your favourite testing framework: MSTest, NUnit or XUnit. Code coverage has always been a trigger for me to always write unit tests for my applications - just like your car, your application is going to break some day, and to prevent that you bring it to the corner garage once in a while to get a checkup. So, back to MSTest and code coverage: what do we need in order to create a code coverage report? In this blog post, I will teach you from A to Z how you can create code coverage reports for yourself. Please note that all your unit tests need to be run on a Windows host, since we rely on utilities with prebuilt executable files that do simply not run on other platforms.
The packages that we need
In order to execute, write and process our tests we need three packages. I usually install them using PowerShell via the package manager, or with the .NET CLI. The packages that we need are:
- Microsoft.NET.Test.Sdk - the foundation for running tests in a .NET (Core) environment.
- MSTest.TestFramework - the testing framework used in this tutorial: MSTest.
- MSTest.TestAdapter - the chainlink between the testing SDK and our testing framework of choice.
Got an error during installation? Don't worry, nuget can get funky from time to time, so please double check your package sources with
dotnet nuget list source or check if you have the right SDK installed with
Decide what your directory structure is going to look like
Your project is most likely already filled with your existing code, so deciding what your test directory structure will look like before writing your tests is not a bad statement. This is what mine looks like:
It consists of a code source file which contains all my testing code. I might split this into multiple source files later on once my application grows, but that's not so important for now. The runsettings file is a settings file for the testing SDK, containing all settings used during testing. It replaced testsettings files which last appeared in Visual Studio 2010. Here is my runsettings file:
<?xml version="1.0" encoding="utf-8"?> <RunSettings> <RunConfiguration> <MaxCpuCount>1</MaxCpuCount> <ResultsDirectory>.\TestResults</ResultsDirectory> <TreatNoTestsAsError>true</TreatNoTestsAsError> </RunConfiguration> <DataCollectionRunSettings> <DataCollectors> <DataCollector friendlyName="Code Coverage" uri="datacollector://Microsoft/CodeCoverage/2.0" assemblyQualifiedName="Microsoft.VisualStudio.Coverage.DynamicCoverageDataCollector, Microsoft.VisualStudio.TraceCollector, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> <Configuration> <CodeCoverage> <ModulePaths> <Exclude> <ModulePath>.*uppeteer.*</ModulePath> </Exclude> </ModulePaths> <UseVerifiableInstrumentation>True</UseVerifiableInstrumentation> <AllowLowIntegrityProcesses>True</AllowLowIntegrityProcesses> <CollectFromChildProcesses>True</CollectFromChildProcesses> <CollectAspDotNet>False</CollectAspDotNet> </CodeCoverage> </Configuration> </DataCollector> </DataCollectors> </DataCollectionRunSettings> </RunSettings>
The most important parameter - in my case atleast - is specifying the ModulePaths. I use PuppeteerSharp as a library in my application and for some reason it includes it during unit testing. I only want my own application to be tested and with a runsettings file you can fix this. It supports regular expressions and you can use as many ModulePath tags as you want. You can even use the Include tag instead of the Exclude tag to just include your own code if you know how to figure that out.
The RunTests.ps1 file is - as you probably already guessed it - a PowerShell script to run everything at once in sequential order. You can just copy and paste it:
Remove-Item .\Tests\TestResults -Recurse -ErrorAction Ignore Write-Host "Running tests..." dotnet test --collect:"Code Coverage" --settings:Tests/settings.runsettings $TestResultFolder = (Get-ChildItem .\Tests\TestResults).Name $TestResultFile = (Get-ChildItem .\Tests\TestResults\$TestResultFolder).Name CodeCoverage analyze /output:Tests\TestResults\results.xml Tests\TestResults\$TestResultFolder\$TestResultFile dotnet reportgenerator "-reports:Tests\TestResults\results.xml" "-targetdir:Tests\TestResults\target" Invoke-Item Tests\TestResults\target\index.html
The script makes a call to a binary made by Microsoft called CodeCoverage. You can download it by downloading and extracting it from a NuGet package and extracting the nupkg file with 7-Zip. If this does not work, you can download it from here as a zip file.
You can either drag all the files in the directory where CodeCoverage.exe is located to the Tests folder or you could do it more elegantly by creating a directory somewhere and adding that folder to your PATH variable - it's up to you!
Secondly, you need dotnet-reportgenerator-globaltool. In order to install this, run these commands on your machine in a new command prompt:
dotnet tool install -g dotnet-reportgenerator-globaltool dotnet tool install dotnet-reportgenerator-globaltool --tool-path tools dotnet new tool-manifest dotnet tool install dotnet-reportgenerator-globaltool
Navigate to the root of your project and run
powershell Tests\RunTests.ps1 to execute the script. At the end, a browser window should pop up with a clear but rather frontpage-looking page:
As always, I hope you learned something and have a nice day!