Loading...
Loading...
Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.
npx skill4agent add aaronontheweb/dotnet-skills crap-analysis| CRAP Score | Risk Level | Action Required |
|---|---|---|
| < 5 | Low | Well-tested, maintainable code |
| 5-30 | Medium | Acceptable but watch complexity |
| > 30 | High | Needs tests or refactoring |
| Method | Complexity | Coverage | Calculation | CRAP |
|---|---|---|---|---|
| 1 | 0% | 1 x (1 - 0)^2 | 1 |
| 54 | 52% | 54 x (1 - 0.52)^2 | 12.4 |
| 20 | 0% | 20 x (1 - 0)^2 | 20 |
| 45 | 20% | 45 x (1 - 0.20)^2 | 28.8 |
| 80 | 10% | 80 x (1 - 0.10)^2 | 64.8 |
coverage.runsettings<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<!-- OpenCover format includes cyclomatic complexity for CRAP scores -->
<Format>cobertura,opencover</Format>
<!-- Exclude test and benchmark assemblies -->
<Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude>
<!-- Exclude generated code, obsolete members, and explicit exclusions -->
<ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
<!-- Exclude source-generated files, Blazor generated code, and migrations -->
<ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile>
<!-- Exclude test projects -->
<IncludeTestAssembly>false</IncludeTestAssembly>
<!-- Optimization flags -->
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<SkipAutoProps>true</SkipAutoProps>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>| Option | Purpose |
|---|---|
| Must include |
| Exclude test/benchmark assemblies by pattern |
| Skip generated, obsolete, and explicitly excluded code (includes |
| Skip source-generated files, Blazor components, and migrations |
| Don't count auto-properties as branches |
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "5.4.5",
"commands": ["reportgenerator"],
"rollForward": false
}
}
}dotnet tool restoredotnet tool install --global dotnet-reportgenerator-globaltool# Clean previous results
rm -rf coverage/ TestResults/
# Run unit tests with coverage
dotnet test tests/MyApp.Tests.Unit \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
# Run integration tests (optional, adds to coverage)
dotnet test tests/MyApp.Tests.Integration \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResultsdotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary;MarkdownSummaryGithub"| Type | Description | Output |
|---|---|---|
| Full interactive report | |
| Plain text summary | |
| GitHub-compatible markdown | |
| SVG badges for README | |
| Merged Cobertura XML | |
Risk Hotspots
─────────────
Method Complexity Coverage Crap Score
──────────────────────────────────────────────────────────────────
DataImporter.ParseRecord() 54 52% 12.4
AuthService.ValidateToken() 32 0% 32.0 ← HIGH RISK
OrderProcessor.Calculate() 28 85% 1.3
UserService.CreateUser() 15 100% 0.0ValidateToken()ParseRecord()CreateUser()Calculate()| Coverage Type | Target | Action |
|---|---|---|
| Line Coverage | > 80% | Good for most projects |
| Branch Coverage | > 60% | Catches conditional logic |
| CRAP Score | < 30 | Maximum for new code |
coverage.props<Project>
<PropertyGroup>
<!-- Coverage thresholds for CI enforcement -->
<CoverageThresholdLine>80</CoverageThresholdLine>
<CoverageThresholdBranch>60</CoverageThresholdBranch>
</PropertyGroup>
</Project>name: Coverage
on:
pull_request:
branches: [main, dev]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Restore tools
run: dotnet tool restore
- name: Run tests with coverage
run: |
dotnet test \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
- name: Generate report
run: |
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;MarkdownSummaryGithub;Cobertura"
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
- name: Add coverage to PR
uses: marocchino/sticky-pull-request-comment@v2
with:
path: coverage/SummaryGithub.md- task: DotNetCoreCLI@2
displayName: 'Run tests with coverage'
inputs:
command: 'test'
arguments: '--settings coverage.runsettings --collect:"XPlat Code Coverage" --results-directory $(Build.SourcesDirectory)/TestResults'
- task: DotNetCoreCLI@2
displayName: 'Generate coverage report'
inputs:
command: 'custom'
custom: 'reportgenerator'
arguments: '-reports:"$(Build.SourcesDirectory)/TestResults/**/coverage.opencover.xml" -targetdir:"$(Build.SourcesDirectory)/coverage" -reporttypes:"HtmlInline_AzurePipelines;Cobertura"'
- task: PublishCodeCoverageResults@2
displayName: 'Publish coverage'
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Build.SourcesDirectory)/coverage/Cobertura.xml'# Full analysis workflow
rm -rf coverage/ TestResults/ && \
dotnet test --settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults && \
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary"
# View summary
cat coverage/Summary.txt
# Open HTML report (Linux)
xdg-open coverage/index.html
# Open HTML report (macOS)
open coverage/index.html
# Open HTML report (Windows)
start coverage/index.html| Metric | New Code | Legacy Code |
|---|---|---|
| Line Coverage | 80%+ | 60%+ (improve gradually) |
| Branch Coverage | 60%+ | 40%+ (improve gradually) |
| Maximum CRAP | 30 | Document exceptions |
| High-risk methods | Must have tests | Add tests before modifying |
coverage.runsettings| Pattern | Reason |
|---|---|
| Test assemblies aren't production code |
| Benchmark projects |
| Database migrations (generated) |
| Source generators |
| Compiler-generated code |
| Explicit developer opt-out |
| Generated files |
| Blazor component generated code |
| Blazor CSS isolation generated code |
| EF Core migrations (auto-generated) |
| Auto-properties (trivial branches) |