I recently started using Coverlet for code coverage in some projects, the projects are all hosted on GitHub or Azure DevOps and build using MSBuild and Azure Pipelines. In this post I will describe how I'm using it.

What is Coverlet

Coverlet is a cross platform code coverage framework for .NET, with support for line, branch and method coverage. It works with .NET Framework on Windows and .NET Core on all supported platforms.

MSBuild Integration

Coverlet also integrates with the build system to run code coverage after tests. Enabling code coverage is as simple as setting the CollectCoverage property to true.

> dotnet test /p:CollectCoverage=true

Add Coverlet to the test projects

To add Coverlet to your test project use the following command.

> dotnet add package coverlet.msbuild

Directory.Build.props

You can also add it to all of your test projects at once by adding a Directory.Build.props file to your tests folder. Using Directory.Build.props files you can add a new property to every project in one step by defining it in the root folder that contains your source. When MSBuild runs, Microsoft.Common.props searches your directory structure for the Directory.Build.props file (and Microsoft.Common.targets looks for Directory.Build.targets). If it finds one, it imports the property.

<Project>
    <ItemGroup>
        <PackageReference Include="coverlet.msbuild" Version="2.6.3">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
    </ItemGroup>
</Project>

Coverage output

Coverlet can generate coverage results in multiple formats, which is specified using the CoverletOutputFormat property. For example, the following command emits coverage results in the cobertura format.

> dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura

Include and/or exclude code

You can ignore a method or an entire class from code coverage by creating and applying the ExcludeFromCodeCoverage attribute present in the System.Diagnostics.CodeAnalysis namespace.

Coverlet also gives you the ability to have fine grained control over what gets excluded or included using "filter expressions". In the following example we include all projects that start with CompanyName., and exclude all projects that end with *Tests.

> dotnet test /p:CollectCoverage=true /p:Include="[CompanyName.*]*" /p:Exclude="[*Tests]*"

Note: To exclude or include multiple assemblies when using Powershell scripts or creating a .yaml file for a Azure DevOps build %2c should be used as a separator. MSBuild will translate this symbol to ,.

Azure Pipelines

In a Azure Pipeline you can use the .NET Core CLI task to run your tests. You have to add some extra arguments to enable the coverage.

Configure the test to collect coverage

To run the code coverage in our Azure Pipeline we need to configure the .NET Core CLI task. We can use the arguments from the dotnet test command.

- task: DotNetCoreCLI@2
  displayName: Test
  inputs:
    command: test
    projects: |
      **/tests/*.Tests/*.csproj
    arguments: '--configuration $(buildConfiguration) /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:Include="[ProjectName.*]*" /p:Exclude="[*Tests]*"'

Generate the code coverage report

To generate the code coverage report from the individual code coverage results per test project, you can use the ReportGenerator task. ReportGenerator converts coverage reports generated by a number of reporters into human readable reports in various formats.

- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
  displayName: Generate Code Coverage Report
  inputs:
    reports: $(Build.SourcesDirectory)/tests/**/coverage.cobertura.xml
    targetdir: $(build.artifactstagingdirectory)/TestResults/
    reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges'

Publish the code coverage results

Publish the code coverage results with the Publish Code Coverage Results task

- task: PublishCodeCoverageResults@1
  displayName: 'Publish Code Coverage Results'
  inputs:
    codeCoverageTool: cobertura
    summaryFileLocation: $(build.artifactstagingdirectory)/TestResults/cobertura.xml
    # To make the task not regenerate the report an environment variable was added to the pipeline in Azure DevOps; "disable.coverage.autogenerate: 'true'"
    # see: https://github.com/danielpalme/ReportGenerator/wiki/Integration#attention
    reportDirectory: '$(build.artifactstagingdirectory)/TestResults'

The Publish Code Coverage Results task from Microsoft regenerates the report with different settings and based on the supplied Coberatura file. Moreover it does not necessarily use the latest version of ReportGenerator. To disable the regeneration of the report, you need to use the following environment variable in your build (in Azure DevOps).

disable.coverage.autogenerate: 'true'

Environment variable