Skip to content

Working with Code Coverage

davidkallesen edited this page Jul 1, 2026 · 1 revision

πŸ§ͺ Working with Code Coverage

This guide shows how to run code coverage for a project that uses the generator, and how to keep generated code out of your coverage numbers.

πŸ“– Overview

Every type the generator emits is annotated with [GeneratedCode("Atc.Rest.Api.SourceGenerator", "<version>")]. That attribute alone doesn't exclude anything from coverage β€” it just marks the code as generated. To keep generated models, endpoints, results, and DI extensions out of your coverage report, you need one of:

Option Scope How
🏷️ excludeFromCodeCoverage marker option This project's generated output only Adds [ExcludeFromCodeCoverage] next to [GeneratedCode] on every generated type
βš™οΈ .runsettings attribute exclusion Any generated/compiler-generated code, project-wide Tells the coverage collector to skip anything carrying [GeneratedCode], [ExcludeFromCodeCoverage], or [CompilerGenerated]

They're complementary β€” the marker option needs no test-project setup and works the moment you enable it; the .runsettings file also silently covers hand-written code you've explicitly marked [ExcludeFromCodeCoverage], and any other source generator's [GeneratedCode] output in the same solution.


▢️ Running Coverage

Test projects need the Microsoft.Testing.Extensions.CodeCoverage package (already wired into this repo's test/Directory.Build.props for every test project; add it yourself for consumer projects):

dotnet add package Microsoft.Testing.Extensions.CodeCoverage

Then run:

dotnet test --coverage

This produces a .coverage file under TestResults/. Useful flags:

# Cobertura XML instead of the binary .coverage format (for CI / report tooling)
dotnet test --coverage --coverage-output-format cobertura

# Custom output path
dotnet test --coverage --coverage-output ./coverage/results.cobertura.xml

# Apply exclusion rules from a settings file (see below)
dotnet test --coverage --coverage-settings coverage.runsettings

πŸ’‘ Tip: Pipe Cobertura output into ReportGenerator (dotnet tool install -g dotnet-reportgenerator-globaltool) to get an HTML coverage report.


🏷️ Option 1: excludeFromCodeCoverage Marker Option

Set excludeFromCodeCoverage: true in any marker file β€” .atc-rest-api-server, .atc-rest-api-server-handlers, or .atc-rest-api-client:

{
  "generate": true,
  "excludeFromCodeCoverage": true
}

Every generated type that carries [GeneratedCode] now also gets [ExcludeFromCodeCoverage]:

// <auto-generated />
#nullable enable

using System.CodeDom.Compiler;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;

namespace PetStore;

[GeneratedCode("Atc.Rest.Api.SourceGenerator", "1.0.341")]
[ExcludeFromCodeCoverage]
public static class ServiceCollectionEndpointHandlerExtensions
{
    ...
}

[ExcludeFromCodeCoverage] isn't valid on every declaration kind β€” the C# compiler only allows it on classes, structs, records, constructors, methods, properties, and events. Interfaces, enums, and delegates (e.g. IEndpointDefinition, handler interfaces) keep [GeneratedCode] without it; there's nothing to exclude on a type with no executable body.

Default is false β€” existing projects are unaffected until you opt in. Set it independently per marker file; a Contracts project can exclude generated code from coverage while a hand-written Domain project doesn't (or vice versa).

πŸ’‘ See Marker Files for the full option reference across all three marker types.


βš™οΈ Option 2: .runsettings Attribute Exclusion

If you'd rather exclude generated code project-wide β€” without touching every marker file, and covering any other source generator's output too β€” configure the Microsoft Code Coverage collector to skip specific attributes.

Create coverage.runsettings:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="Code Coverage" uri="datacollector://microsoft/CodeCoverage/2.0">
        <Configuration>
          <CodeCoverage>
            <Attributes>
              <Exclude>
                <!-- Universal opt-out β€” respected by all coverage tools -->
                <Attribute>System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute</Attribute>
                <!-- Source generators, Dataverse Model Builder, ApiGenerator, etc. -->
                <Attribute>System.CodeDom.Compiler.GeneratedCodeAttribute</Attribute>
                <!-- C# compiler internals: async state machines, lambdas, iterators, records -->
                <Attribute>System.Runtime.CompilerServices.CompilerGeneratedAttribute</Attribute>
              </Exclude>
            </Attributes>
          </CodeCoverage>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

Then reference it when running tests:

dotnet test --coverage --coverage-settings coverage.runsettings

Because every type this generator emits already carries [GeneratedCode] unconditionally, excluding System.CodeDom.Compiler.GeneratedCodeAttribute removes all generated output from coverage β€” no marker file changes needed. The other two attributes are there for completeness:

  • ExcludeFromCodeCoverageAttribute β€” hand-written code you've explicitly opted out (or generated code with excludeFromCodeCoverage: true set, redundantly β€” either attribute alone is enough)
  • CompilerGeneratedAttribute β€” compiler-synthesized members (async state machines, closures, primary constructors, record equality members) that aren't yours to test either

⚠️ Excluding GeneratedCodeAttribute project-wide also excludes output from any other source generator in the solution (EF Core, MediatR, etc.) that follows the same convention. Use the marker option instead if you only want this generator's output excluded.


πŸ”€ Choosing Between the Two

Scenario Use
Only want this generator's output excluded, no build/CI changes 🏷️ excludeFromCodeCoverage marker option
Want generated code excluded across the whole solution, including other generators βš™οΈ .runsettings
CI pipeline already has a shared .runsettings for other exclusion rules βš™οΈ .runsettings β€” add the three attributes to your existing file
Different exclusion policy per project (Contracts excluded, Domain not) 🏷️ excludeFromCodeCoverage marker option, set per marker file

They can also be combined β€” a .runsettings exclusion is a safety net that keeps working even if a marker file's excludeFromCodeCoverage is left at its false default.

πŸ”— Related Guides

🏠 Home

πŸ’Ό Why This Tool?

πŸ“– Getting Started

βš™οΈ Features

🌐 Frontend

πŸ“‹ Reference


πŸ”— Resources

Clone this wiki locally