Code coverage metrics are essential for maintaining code quality and ensuring adequate test coverage. However, .NET 10’s extensive code generation can create false negatives in coverage reports, leading to unnecessary work and skewed metrics. This guide shows you how to configure Coverlet to focus on the code that matters: the code your team actually writes.
An Interesting Problem
Whilst working on a project using Azure DevOps, .NET 10, and Coverlet, I encountered an unusual issue. Every pull request was required to include code coverage, so I added the following YAML to the azure-pipelines.yml file:
- task: DotNetCoreCLI@2
displayName: 'Run all tests & collect coverage'
inputs:
command: 'test'
projects: '<REDACTED>'
arguments: '--configuration Release --no-build --no-restore --logger trx --collect:"XPlat Code Coverage" --results-directory $(Agent.TempDirectory)/TestResults'
The configuration worked as expected, but when I reviewed the Code Coverage tab for the build, something unexpected appeared. The code coverage table listed numerous .NET and ASP .NET Core namespaces alongside the team’s actual code:
- Microsoft.AspNetCore.OpenApi.Generated
- System.Runtime.CompilerServices
- Various other framework-specific namespaces related to auto-generated code
More concerning, each of these auto-generated namespaces showed 0% code coverage.
The decision makers saw this as a red flag and began creating work items specifically to add tests for the auto-generated code. It doesn’t take long to realise that writing tests for compiler-generated code would be both a waste of time and a moving target; who knows whether changes to the compiler or runtime would cause such tests to break?
After some investigation, I discovered several ways to exclude this auto-generated code from coverage reports.
Some Background
In case you weren’t aware, .NET 10 performs substantial work behind the scenes by generating considerable boilerplate code automatically. This isn’t entirely new; the C# compiler has been generating code since .NET 6, when Microsoft introduced top level statements.
However, .NET 10 has significantly expanded the scope of auto-generated code. From automatically generating the public partial Program class required for integration tests to comprehensive OpenAPI implementations, the C# compiler now handles substantially more boilerplate code on our behalf.
Crucially, since .NET 6 at least, all auto-generated code that the C# compiler creates is decorated with the [GeneratedCodeAttribute] attribute. Stephen Toub (author of the comprehensive yearly .NET performance blog posts) confirmed this in an October 2023 GitHub discussion.
This attribute provides the key to our solution.
The Solution
The fix is remarkably straightforward. Coverlet provides configuration options through the DataCollectionRunSettings that allow us to exclude code based on attributes. By adding the ExcludeByAttribute parameter with the value GeneratedCodeAttribute, we instruct Coverlet to skip any code marked with this attribute during coverage analysis.
Remember the YAML configuration from earlier? We can make a single line change, and Coverlet will completely ignore any code decorated with the [GeneratedCodeAttribute] attribute:
- task: DotNetCoreCLI@2
displayName: 'Run all tests & collect coverage'
inputs:
command: 'test'
projects: '<REDACTED>'
arguments: '--configuration Release --no-build --no-restore --logger trx --collect:"XPlat Code Coverage" --results-directory $(Agent.TempDirectory)/TestResults --DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute="GeneratedCodeAttribute"'
ℹ️ Note
Because the example line is quite long, here’s specifically what I’ve added to the end of the arguments string:
The parameter name is
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttributeAnd the value is
="GeneratedCodeAttribute"Don’t forget to add the
--prefix to the parameter.
The keen-eyed amongst you may have noticed that what I’ve added here looks remarkably similar to a flattened CodeCoverage.runsettings file; that’s exactly what it is.
Alternatively, if you prefer managing configuration separately from your pipeline definition, you can create a dedicated CodeCoverage.runsettings file and reference it in your pipeline with the --settings parameter. Here’s how that file would look:
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat Code Coverage">
<Configuration>
<ExcludeByAttribute>GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
<ExcludeByAttribute>**/Microsoft.AspNetCore.OpenApi.Generated/**</ExcludeByAttribute>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
ℹ️ Note
As you can see, you’ll need to include more
ExcludeByAttributevalues if you go this route.
I prefer the inline CLI version as it’s easier to understand than XML (very few people enjoy working with XML these days), but I’ve included both approaches for your convenience.
An Important Caveat
⚠️ WARNING
This configuration excludes all code decorated with the
[GeneratedCodeAttribute]attribute, not just .NET framework-generated code.
If your project includes custom code generation (such as source generators or T4 templates), that code will also be excluded from coverage reports. Whilst this is typically desirable for generated code, ensure this aligns with your team’s coverage requirements.
For most projects that don’t implement custom code generation, this approach provides clean coverage reports focused solely on hand-written code.
Alternative Approaches
If you’re using DotCover from JetBrains for code coverage analysis, it automatically excludes generated code from reports by default. However, DotCover does include test classes unless you explicitly exclude them by adding the following to your test project file:
<AssemblyAttribute Include="System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute" />
Summary
✅ .NET 10’s extensive code generation can pollute coverage reports with 0% coverage entries
✅ Coverlet can exclude generated code via the ExcludeByAttribute configuration
✅ Apply the exclusion through inline YAML parameters or a separate runsettings file
✅ Be aware that custom-generated code will also be excluded
This simple configuration change ensures your code coverage metrics accurately reflect your team’s testing efforts, preventing confusion and unnecessary work items for stakeholders. Your coverage reports will now focus on what matters: the code your team has actually written and is responsible for maintaining.
Is your team spending time on the wrong problems?
This coverage configuration issue is a symptom of a common challenge: distinguishing signal from noise in your development metrics. Our Fractional CTO services help teams focus on what matters, establishing practices that drive genuine quality improvements.
