Measuring Code Coverage in C# Projects

Code coverage measurement determines the percentage of your software's source code executed while running automated test suites, serving as a primary metric for identifying untested paths and assessing overall test thoroughness. In modern C# and .NET development, measuring coverage involves tracking statement, method, and branch execution using tools like Coverlet, Visual Studio's native analysis engine, and ReportGenerator. This article covers the fundamental metrics of code coverage, how to collect and analyze this data in C# using command-line and IDE tools, and how to effectively track test thoroughness within automated continuous integration pipelines.

What is Code Coverage Measurement?

Code coverage is a quantitative software metric that analyzes source code execution during the running of automated test suites (such as unit, integration, and functional tests). It identifies the degree to which an application’s code base has been exercised by the tests.

The primary metrics tracked include:

  • Line / Statement Coverage: Measures whether each executable line of code was hit by a test.
  • Branch Coverage: Measures whether each branch of control structures (such as if, else, switch, or pattern matching in C#) was executed in both true and false evaluations.
  • Method Coverage: Tracks the percentage of defined methods invoked during the test execution.

While high code coverage does not guarantee bug-free software, low code coverage explicitly highlights areas containing blind spots and untested business logic.

Core Tools for C# Code Coverage

The .NET ecosystem provides multiple mature tools to collect and visualize coverage data:

  1. Coverlet: A cross-platform code coverage framework for .NET that supports .NET Framework, .NET Core, and modern .NET (5/6/7/8+). It integrates directly with the .NET test runner as an MSBuild task or a data collector.
  2. Visual Studio Code Coverage: Built into Visual Studio Enterprise, offering visual indicators directly in the code editor, identifying covered lines (green), partially covered lines (yellow), and uncovered lines (red).
  3. ReportGenerator: A utility that converts raw coverage output files (XML formats like Cobertura or lcov) into human-readable HTML reports or summary formats for continuous integration (CI) systems.

Collecting Code Coverage in C# with the .NET CLI

The standardized way to gather code coverage across cross-platform .NET environments is via the dotnet test CLI using Coverlet.

Step 1: Add the Data Collector

Add the coverlet.collector NuGet package to your test project:

dotnet add package coverlet.collector

Step 2: Execute Tests and Collect Metrics

Run your test project and specify the coverage data collector flag:

dotnet test --collect:"XPlat Code Coverage"

This command executes your test suite (using xUnit, NUnit, or MSTest) and outputs a raw coverage file, typically named coverage.cobertura.xml, inside a generated TestResults directory.

Step 3: Generate Readable Reports

To transform the raw XML into a navigable HTML report, use the ReportGenerator global tool:

dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html

Opening coveragereport/index.html displays coverage percentages broken down by assembly, class, and method, along with line-by-line source visualizers.

Tracking Test Thoroughness Effectively

Measuring coverage alone does not ensure test quality. To track thoroughness accurately, apply the following practices:

Prioritize Branch Coverage Over Line Coverage

In C#, constructs such as null-coalescing operators (??), ternary expressions, and complex LINQ queries compress significant logic into single lines. Line coverage can report a line as 100% covered even if only one condition was tested. Tracking branch coverage reveals whether every logical decision path has been exercised.

Exclude Non-Domain Code

Raw coverage numbers can be skewed by boilerplate code. Use the [ExcludeFromCodeCoverage] attribute or configuration settings in runsettings files to omit:

  • Auto-generated files (e.g., entity framework migrations, gRPC stubs).
  • Pure configuration setup and dependency injection registries.
  • Data transfer objects (DTOs) without business logic.

Enforce Coverage Thresholds in CI/CD

To prevent untested regressions, automate coverage checks in your continuous integration pipelines (GitHub Actions, Azure DevOps, GitLab CI). You can configure Coverlet to fail a build if coverage falls below a target percentage:

dotnet test /p:CollectCoverage=true /p:Threshold=80 /p:ThresholdType=branch

If the test suite executes with branch coverage below 80%, the build pipeline halts, ensuring that new features adhere to quality standards before merging.