.NET Core Code Coverage

I haven’t written anything for a while because, frankly, I’m passed the platform R&D stages of my current application and just churning out features, and so far I haven’t found anything much to inspire me to write about. After digging through my code looking at things I’d done, one area I thought may be interesting to readers is getting (free) code coverage in .NET Core.

OpenCover

The open source tool of choice for code coverage seems to be OpenCover. This comes as a nice zip file which can be extracted anywhere. Getting set up for .NET Core was mostly a case of following various instructions online, and there was just one gotcha: the MSBuild DebugType must be full, which is typically not the case for .NET Core where the goal is deployment to multiple operating systems. To get around this my coverage script overwrites the .proj file before running and puts portable back when it is done.

The script runs the dotnet executable from the assembly folder, meaning the assemblies aren’t directly specified in the script. The graphical output of the coverage is put together using ReportGenerator, which I have deployed inside my report output folder.

Here is a cut-down version of my Powershell script:

Push-Location

# change portable to full for projects
$csprojFiles = gci [Repository-Path] -Recurse -Include *.csproj
$csprojFiles | %{
     (Get-Content $_ | ForEach  { $_ -replace 'portable', 'full' }) | 
     Set-Content $_
}

# Setup filter to exclude classes with no methods
$domainNsToInclude = @("MyNamespace.Auth.*", "MyNamespace.Data.*")
# Combine [assembly] with namespaces
$domainFilter = '+[AssemblyPrefix.Domain]' + ($domainNsToInclude -join ' +[AssemblyPrefix.Domain]')
$filter = "+[AssemblyPrefix.Api]* $domainFilter"

# Integration Test Project
$integrationOutput = "[output-path]\Integration.xml"
cd "D:\Code\RepositoryRoot\test\integration"
dotnet build
[open-cover-path]\OpenCover.Console.exe `
    -register:user `
    -oldStyle `
    "-target:C:\Program Files\dotnet\dotnet.exe" `
    "-targetargs:test" `
    "-filter:$filter" `
    "-output:$integrationOutput" `
    -skipautoprops

# Generate Report
$reportFolder = "[output-path]\ReportFolder"
[report-generator-path]\ReportGenerator.exe `
    "-reports:$integrationOutput" `
    "-targetdir:$reportFolder"

# restore portable in projects
$csprojFiles | %{
     (Get-Content $_ | ForEach  { $_ -replace 'full', 'portable' }) |
     Set-Content $_
}

Pop-Location

The end result after opening the index.html is something like this (looks like I need to work on that branch coverage!):
coverage