CRAP-Score-Analyse
Wann dieser Skill verwendet werden sollte
Verwenden Sie diesen Skill, wenn:
- Sie die Codequalität und Testabdeckung vor Änderungen bewerten möchten
- Sie risikoreichen Code identifizieren möchten, der umgestaltet oder getestet werden muss
- Sie die Coverage-Erfassung für ein .NET-Projekt einrichten möchten
- Sie priorisieren möchten, welcher Code basierend auf Risiko getestet werden soll
- Sie Coverage-Schwellenwerte für CI/CD-Pipelines festlegen möchten
Was ist CRAP?
CRAP-Score = Komplexität x (1 - Abdeckung)^2
Der CRAP-Score (Change Risk Anti-Patterns) kombiniert die zyklomatische Komplexität mit der Testabdeckung, um risikoreichen Code zu identifizieren.
| CRAP-Score | Risikostufe | Erforderliche Maßnahme |
|---|---|---|
| < 5 | Niedrig | Gut getesteter, wartbarer Code |
| 5-30 | Mittel | Akzeptabel, aber Komplexität im Auge behalten |
| > 30 | Hoch | Benötigt Tests oder Refactoring |
Warum CRAP wichtig ist
- Hohe Komplexität + niedrige Abdeckung = Gefahr: Code, der schwer zu verstehen UND ungetestet ist, ist riskant zu ändern
- Komplexität allein reicht nicht: Eine komplexe Methode mit 100% Abdeckung ist sicherer als eine einfache Methode mit 0%
- Fokussiert den Aufwand: Priorisieren Sie das Testen von komplexem Code, nicht von einfachen Gettern/Setzern
CRAP-Score-Beispiele
| Methode | Komplexität | Abdeckung | Berechnung | CRAP |
|---|---|---|---|---|
GetUserId() | 1 | 0% | 1 x (1 - 0)^2 | 1 |
ParseToken() | 54 | 52% | 54 x (1 - 0.52)^2 | 12.4 |
ValidateForm() | 20 | 0% | 20 x (1 - 0)^2 | 20 |
ProcessOrder() | 45 | 20% | 45 x (1 - 0.20)^2 | 28.8 |
ImportData() | 80 | 10% | 80 x (1 - 0.10)^2 | 64.8 |
Einrichtung der Coverage-Erfassung
coverage.runsettings
Erstellen Sie eine coverage.runsettings-Datei im Stammverzeichnis Ihres Repositorys. Das OpenCover-Format ist für die CRAP-Score-Berechnung erforderlich, da es Metriken zur zyklomatischen Komplexität enthält.
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<!-- OpenCover-Format enthält zyklomatische Komplexität für CRAP-Scores -->
<Format>cobertura,opencover</Format>
<!-- Test- und Benchmark-Assemblys ausschließen -->
<Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude>
<!-- Generierten Code, obsolete Member und explizite Ausschlüsse ausschließen -->
<ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
<!-- Quellgenerierte Dateien, Blazor-generierten Code und Migrationen ausschließen -->
<ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile>
<!-- Testprojekte ausschließen -->
<IncludeTestAssembly>false</IncludeTestAssembly>
<!-- Optimierungsflags -->
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<SkipAutoProps>true</SkipAutoProps>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Wichtige Konfigurationsoptionen
| Option | Zweck |
|---|---|
Format | Muss opencover für Komplexitätsmetriken enthalten |
Exclude | Test-/Benchmark-Assemblys nach Muster ausschließen |
ExcludeByAttribute | Generierten, veralteten und explizit ausgeschlossenen Code überspringen (einschließlich ExcludeFromCodeCoverageAttribute) |
ExcludeByFile | Quellgenerierte Dateien, Blazor-Komponenten und Migrationen überspringen |
SkipAutoProps | Auto-Eigenschaften nicht als Verzweigungen zählen |
ReportGenerator-Installation
Installieren Sie ReportGenerator als lokales Tool, um HTML-Berichte mit Risiko-Hotspots zu erstellen.
Zu .config/dotnet-tools.json hinzufügen
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "5.4.5",
"commands": ["reportgenerator"],
"rollForward": false
}
}
}
Dann wiederherstellen:
dotnet tool restore
Oder global installieren
dotnet tool install --global dotnet-reportgenerator-globaltool
Coverage erfassen
Tests mit Coverage-Erfassung ausführen
# Vorherige Ergebnisse löschen
rm -rf coverage/ TestResults/
# Unit-Tests mit Coverage ausführen
dotnet test tests/MyApp.Tests.Unit \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
# Integrationstests ausführen (optional, erweitert die Coverage)
dotnet test tests/MyApp.Tests.Integration \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
HTML-Bericht erstellen
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary;MarkdownSummaryGithub"
Berichtstypen
| Typ | Beschreibung | Ausgabe |
|---|---|---|
Html | Vollständiger interaktiver Bericht | coverage/index.html |
TextSummary | Reine Textzusammenfassung | coverage/Summary.txt |
MarkdownSummaryGithub | GitHub-kompatibles Markdown | coverage/SummaryGithub.md |
Badges | SVG-Badges für README | coverage/badge_*.svg |
Cobertura | Zusammengeführtes Cobertura-XML | coverage/Cobertura.xml |
Den Bericht lesen
Abschnitt „Risk Hotspots“
Der HTML-Bericht enthält einen Abschnitt Risk Hotspots, der Methoden sortiert nach Komplexität anzeigt:
- Zyklomatische Komplexität: Anzahl der unabhängigen Pfade durch den Code (if/else, switch cases, Schleifen)
- NPath-Komplexität: Anzahl der azyklischen Ausführungspfade (exponentielles Wachstum bei Verschachtelung)
- Crap-Score: Berechnet aus Komplexität und Coverage
Ergebnisse interpretieren
Risk Hotspots
─────────────
Method Complexity Coverage Crap Score
──────────────────────────────────────────────────────────────────
DataImporter.ParseRecord() 54 52% 12.4
AuthService.ValidateToken() 32 0% 32.0 ← HOHES RISIKO
OrderProcessor.Calculate() 28 85% 1.3
UserService.CreateUser() 15 100% 0.0
Aktionspunkte:
ValidateToken()hat einen CRAP > 30 bei 0% Coverage – sofort testen oder umgestaltenParseRecord()ist komplex, hat aber eine ordentliche Coverage – akzeptabelCreateUser()undCalculate()sind gut getestet – sicher zu ändern
Coverage-Schwellenwerte
Empfohlene Standards
| Coverage-Typ | Ziel | Aktion |
|---|---|---|
| Zeilenabdeckung | > 80% | Gut für die meisten Projekte |
| Verzweigungsabdeckung | > 60% | Erfasst bedingte Logik |
| CRAP-Score | < 30 | Maximal für neuen Code |
Schwellenwerte konfigurieren
Erstellen Sie coverage.props in Ihrem Repository:
<Project>
<PropertyGroup>
<!-- Coverage-Schwellenwerte für CI-Erzwingung -->
<CoverageThresholdLine>80</CoverageThresholdLine>
<CoverageThresholdBranch>60</CoverageThresholdBranch>
</PropertyGroup>
</Project>
CI/CD-Integration
GitHub Actions
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
Azure Pipelines
- 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'
Kurzreferenz
Einzeiler-Befehle
# Vollständiger Analyse-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"
# Zusammenfassung anzeigen
cat coverage/Summary.txt
# HTML-Bericht öffnen (Linux)
xdg-open coverage/index.html
# HTML-Bericht öffnen (macOS)
open coverage/index.html
# HTML-Bericht öffnen (Windows)
start coverage/index.html
Projektstandards
| Metrik | Neuer Code | Legacy-Code |
|---|---|---|
| Zeilenabdeckung | 80%+ | 60%+ (schrittweise verbessern) |
| Verzweigungsabdeckung | 60%+ | 40%+ (schrittweise verbessern) |
| Maximaler CRAP | 30 | Ausnahmen dokumentieren |
| Hochrisiko-Methoden | Müssen Tests haben | Tests vor Änderungen hinzufügen |
Was ausgeschlossen wird
Die empfohlene coverage.runsettings schließt aus:
| Muster | Grund |
|---|---|
[*.Tests]* | Test-Assemblys sind kein Produktionscode |
[*.Benchmark]* | Benchmark-Projekte |
[*.Migrations]* | Datenbankmigrationen (generiert) |
GeneratedCodeAttribute | Quellgeneratoren |
CompilerGeneratedAttribute | Compiler-generierter Code |
ExcludeFromCodeCoverageAttribute | Expliziter Entwickler-Opt-out |
*.g.cs, *.designer.cs | Generierte Dateien |
*.razor.g.cs | Generierter Blazor-Komponentencode |
*.razor.css.g.cs | Generierter Code für Blazor-CSS-Isolation |
**/Migrations/**/* | EF Core-Migrationen (automatisch generiert) |
SkipAutoProps | Auto-Eigenschaften (triviale Verzweigungen) |
Wann Schwellenwerte aktualisiert werden sollten
Schwellenwerte vorübergehend senken für:
- Legacy-Codebasen, die modernisiert werden (in README dokumentieren)
- Generierten Code, der nicht geändert werden kann
- Drittanbieter-Wrapper-Code
Schwellenwerte niemals senken für:
- „Es ist zu schwer zu testen“ – stattdessen umgestalten
- „Wir fügen Tests später hinzu“ – jetzt hinzufügen
- Neue Funktionen – sollten von Anfang an Standards erfüllen
Zusätzliche Ressourcen
- Coverlet-Dokumentation: https://github.com/coverlet-coverage/coverlet
- ReportGenerator: https://github.com/danielpalme/ReportGenerator
- CRAP-Score Originalarbeit: http://www.artima.com/weblogs/viewpost.jsp?thread=215899