Use the open-source free `coverlet` toolchain for .NET code coverage.
CRAP Score Analysis
Calculates targeted CRAP (Change Risk Anti-Patterns) scores for a named .NET method, class, or single source file. Use when the user explicitly asks to compute CRAP scores or assess risky untested code for a specific target, combining Cobertura coverage data with cyclomatic complexity analysis. DO NOT USE FOR: project-wide coverage analysis, coverage plateau or "stuck coverage" diagnosis, what's blocking coverage, or where to add tests across a project (use coverage-analysis); writing tests; running tests without CRAP context.
Workflow
Step 1: Collect code coverage data
If no coverage data exists yet (no Cobertura XML available), always run `dotnet test` with coverage collection first and mention the exact command in your response. Do not skip this step -- CRAP scores require coverage data.
Check the test project's .csproj for the coverage package, then run the appropriate command:
| Coverage Package | Command | Output Location | |---|---|---| | coverlet.collector | dotnet test --collect:"XPlat Code Coverage" --results-directory ./TestResults | Typically under TestResults/<guid>/coverage.cobertura.xml. Search recursively under the results directory (for example, TestResults/**/coverage.cobertura.xml) or use any explicit coverage path the user provides. | | Microsoft.Testing.Extensions.CodeCoverage (.NET 9) | dotnet test -- --coverage --coverage-output-format cobertura --coverage-output ./TestResults | --coverage-output path | | Microsoft.Testing.Extensions.CodeCoverage (.NET 10+) | dotnet test --coverage --coverage-output-format cobertura --coverage-output ./TestResults | --coverage-output path |
Step 2: Compute cyclomatic complexity
Analyze the target source files to determine cyclomatic complexity per method. Count the following decision points (each adds 1 to the base complexity of 1):
| Construct | Example | |-----------|---------| | if | if (x > 0) | | else if | else if (y < 0) | | case (each) | case 1: | | for | for (int i = 0; ...) | | foreach | foreach (var item in list) | | while | while (running) | | do...while | do { } while (cond) | | catch (each) | catch (Exception ex) | | && | if (a && b) | | \|\| (OR) | if (a \|\| b) | | ?? | value ?? fallback | | ?. | obj?.Method() | | ? : (ternary) | x > 0 ? a : b | | Pattern match arm | x is > 0 and < 10 |
Base complexity is 1 for every method. Each decision point adds 1.
When analyzing, read the source file and count these constructs per method. Report the breakdown.
Step 3: Extract per-method coverage from Cobertura XML
Parse the Cobertura XML to find each method's line-rate attribute under the target <class> element. If line-rate is not available at method level, compute it from the <lines> elements:
$$\text{cov}(m) = \frac{\text{lines with hits} > 0}{\text{total lines}}$$
Method names in Cobertura may differ from source (async methods, lambdas). Match by line ranges when names don't align.
Step 4: Calculate CRAP scores
For each method in scope, apply the formula:
$$\text{CRAP}(m) = \text{comp}(m)^2 \times (1 - \text{cov}(m))^3 + \text{comp}(m)$$
Step 5: Present results
Present a sorted table (highest CRAP first):
| Method | Complexity | Coverage | CRAP Score | Risk |
|---------------------------------|------------|----------|------------|----------|
| OrderService.ProcessOrder | 12 | 45% | 28.4 | High |
| OrderService.ValidateItems | 8 | 90% | 8.1 | Moderate |
| OrderService.CalculateTotal | 3 | 100% | 3.0 | Low |
Include:
- Summary: total methods analyzed, how many in each risk category
- Top offenders: methods with CRAP > 30, with specific recommendations
- Quick wins: methods with high complexity but where small coverage improvements would drop the score significantly
Step 6: Provide actionable recommendations
For high-CRAP methods, suggest one or both:
- Add tests -- identify uncovered branches and suggest specific test cases
- Reduce complexity -- suggest extract-method refactoring for deeply nested logic
Calculate the coverage needed to bring a method below a CRAP threshold of 15:
$$\text{cov}_{\text{needed}} = 1 - \left(\frac{15 - \text{comp}}{\text{comp}^2}\right)^{1/3}$$
This formula only applies when comp < 15. When comp >= 15, the minimum possible CRAP score (at 100% coverage) is comp itself, which already meets or exceeds the threshold. In that case, coverage alone cannot bring the CRAP score below the threshold -- the method must be refactored to reduce its cyclomatic complexity first.
Report this as: "To bring ProcessOrder (complexity 12) below CRAP 15, increase coverage from 45% to at least 72%." For methods where complexity alone exceeds the threshold, report: "ComplexMethod (complexity 18) cannot reach CRAP < 15 through testing alone -- reduce complexity by extracting sub-methods."
Related skills
Write, run, or repair .NET tests that use MSTest.
Write, run, or repair .NET tests that use NUnit.