$npx -y skills add Aaronontheweb/dotnet-skills --skill crap-analysisAnalyze 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.
| 1 | # CRAP Score Analysis |
| 2 | |
| 3 | ## When to Use This Skill |
| 4 | |
| 5 | Use this skill when: |
| 6 | - Evaluating code quality and test coverage before changes |
| 7 | - Identifying high-risk code that needs refactoring or testing |
| 8 | - Setting up coverage collection for a .NET project |
| 9 | - Prioritizing which code to test based on risk |
| 10 | - Establishing coverage thresholds for CI/CD pipelines |
| 11 | |
| 12 | --- |
| 13 | |
| 14 | ## What is CRAP? |
| 15 | |
| 16 | **CRAP Score = Complexity x (1 - Coverage)^2** |
| 17 | |
| 18 | The CRAP (Change Risk Anti-Patterns) score combines cyclomatic complexity with test coverage to identify risky code. |
| 19 | |
| 20 | | CRAP Score | Risk Level | Action Required | |
| 21 | |------------|------------|-----------------| |
| 22 | | **< 5** | Low | Well-tested, maintainable code | |
| 23 | | **5-30** | Medium | Acceptable but watch complexity | |
| 24 | | **> 30** | High | Needs tests or refactoring | |
| 25 | |
| 26 | ### Why CRAP Matters |
| 27 | |
| 28 | - **High complexity + low coverage = danger**: Code that's hard to understand AND untested is risky to modify |
| 29 | - **Complexity alone isn't enough**: A complex method with 100% coverage is safer than a simple method with 0% |
| 30 | - **Focuses effort**: Prioritize testing on complex code, not simple getters/setters |
| 31 | |
| 32 | ### CRAP Score Examples |
| 33 | |
| 34 | | Method | Complexity | Coverage | Calculation | CRAP | |
| 35 | |--------|------------|----------|-------------|------| |
| 36 | | `GetUserId()` | 1 | 0% | 1 x (1 - 0)^2 | **1** | |
| 37 | | `ParseToken()` | 54 | 52% | 54 x (1 - 0.52)^2 | **12.4** | |
| 38 | | `ValidateForm()` | 20 | 0% | 20 x (1 - 0)^2 | **20** | |
| 39 | | `ProcessOrder()` | 45 | 20% | 45 x (1 - 0.20)^2 | **28.8** | |
| 40 | | `ImportData()` | 80 | 10% | 80 x (1 - 0.10)^2 | **64.8** | |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Coverage Collection Setup |
| 45 | |
| 46 | ### coverage.runsettings |
| 47 | |
| 48 | Create a `coverage.runsettings` file in your repository root. The **OpenCover format is required** for CRAP score calculation because it includes cyclomatic complexity metrics. |
| 49 | |
| 50 | ```xml |
| 51 | <?xml version="1.0" encoding="utf-8" ?> |
| 52 | <RunSettings> |
| 53 | <DataCollectionRunSettings> |
| 54 | <DataCollectors> |
| 55 | <DataCollector friendlyName="XPlat code coverage"> |
| 56 | <Configuration> |
| 57 | <!-- OpenCover format includes cyclomatic complexity for CRAP scores --> |
| 58 | <Format>cobertura,opencover</Format> |
| 59 | |
| 60 | <!-- Exclude test and benchmark assemblies --> |
| 61 | <Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude> |
| 62 | |
| 63 | <!-- Exclude generated code, obsolete members, and explicit exclusions --> |
| 64 | <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute> |
| 65 | |
| 66 | <!-- Exclude source-generated files, Blazor generated code, and migrations --> |
| 67 | <ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile> |
| 68 | |
| 69 | <!-- Exclude test projects --> |
| 70 | <IncludeTestAssembly>false</IncludeTestAssembly> |
| 71 | |
| 72 | <!-- Optimization flags --> |
| 73 | <SingleHit>false</SingleHit> |
| 74 | <UseSourceLink>true</UseSourceLink> |
| 75 | <SkipAutoProps>true</SkipAutoProps> |
| 76 | </Configuration> |
| 77 | </DataCollector> |
| 78 | </DataCollectors> |
| 79 | </DataCollectionRunSettings> |
| 80 | </RunSettings> |
| 81 | ``` |
| 82 | |
| 83 | ### Key Configuration Options |
| 84 | |
| 85 | | Option | Purpose | |
| 86 | |--------|---------| |
| 87 | | `Format` | Must include `opencover` for complexity metrics | |
| 88 | | `Exclude` | Exclude test/benchmark assemblies by pattern | |
| 89 | | `ExcludeByAttribute` | Skip generated, obsolete, and explicitly excluded code (includes `ExcludeFromCodeCoverageAttribute`) | |
| 90 | | `ExcludeByFile` | Skip source-generated files, Blazor components, and migrations | |
| 91 | | `SkipAutoProps` | Don't count auto-properties as branches | |
| 92 | |
| 93 | --- |
| 94 | |
| 95 | ## ReportGenerator Installation |
| 96 | |
| 97 | Install ReportGenerator as a local tool for generating HTML reports with Risk Hotspots. |
| 98 | |
| 99 | ### Add to .config/dotnet-tools.json |
| 100 | |
| 101 | ```json |
| 102 | { |
| 103 | "version": 1, |
| 104 | "isRoot": true, |
| 105 | "tools": { |
| 106 | "dotnet-reportgenerator-globaltool": { |
| 107 | "version": "5.4.5", |
| 108 | "commands": ["reportgenerator"], |
| 109 | "rollForward": false |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | ``` |
| 114 | |
| 115 | Then restore: |
| 116 | |
| 117 | ```bash |
| 118 | dotnet tool restore |
| 119 | ``` |
| 120 | |
| 121 | ### Or Install Globally |
| 122 | |
| 123 | ```bash |
| 124 | dotnet tool install --global dotnet-reportgenerator-globaltool |
| 125 | ``` |
| 126 | |
| 127 | --- |
| 128 | |
| 129 | ## Collecting Coverage |
| 130 | |
| 131 | ### Run Tests with Coverage Collection |
| 132 | |
| 133 | ```bash |
| 134 | # Clean previous results |
| 135 | rm -rf coverage/ TestResults/ |
| 136 | |
| 137 | # Run unit tests with coverage |
| 138 | dotnet test tests/MyApp.Tests.Unit \ |
| 139 | --settings coverage.runsettings \ |
| 140 | --collect:"XPlat Code Coverage" \ |
| 141 | --results-directory ./TestResults |
| 142 | |
| 143 | # Run integration tests (optional, adds to coverage) |
| 144 | dotnet test tests/MyApp.Tests.Integration \ |
| 145 | --settings coverage.runsettings \ |
| 146 | --collect:"XPlat Code Coverage" \ |
| 147 | --results-directory ./TestResults |
| 148 | ``` |
| 149 | |
| 150 | ### Generate HTML Report |
| 151 | |
| 152 | ```bash |
| 153 | dotnet reportgenerator \ |
| 154 | -reports:"TestResults/**/coverage.opencover.xml" \ |
| 155 | -targetdir:"coverag |