Crap Analysis

aaronontheweb/dotnet-skills/skills/crap-analysis

作者 aarononthewebe426ed93a9f3無授權條款1.2K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫3 週前更新

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.

AI 產生的概覽

分析 .NET 程式碼涵蓋率與 CRAP 分數,找出高風險、複雜且測試不足的程式碼。

功能
此技能說明如何計算 CRAP(變更風險反模式)分數,將循環複雜度與測試涵蓋率結合,替高風險程式碼排序。它提供 coverage.runsettings 設定、ReportGenerator 安裝、dotnet test 與 reportgenerator 指令,以及閱讀風險熱點報告的指引。內容也涵蓋涵蓋率門檻、CI/CD 整合範例與排除規則。
適用情境
適用於在變更前評估程式碼品質與測試涵蓋率、找出需要重構或補測試的程式碼、為 .NET 專案建立涵蓋率收集,或為 CI/CD 流程設定涵蓋率門檻。
執行需求
需要 .NET 專案、dotnet CLI、XPlat Code Coverage 收集器與 ReportGenerator 全域工具;複雜度指標需要 OpenCover 格式的涵蓋率輸出。此技能不附帶指令碼,僅為說明與設定。

CRAP Score Analysis

When to Use This Skill

Use this skill when:

  • Evaluating code quality and test coverage before changes
  • Identifying high-risk code that needs refactoring or testing
  • Setting up coverage collection for a .NET project
  • Prioritizing which code to test based on risk
  • Establishing coverage thresholds for CI/CD pipelines

What is CRAP?

CRAP Score = Complexity x (1 - Coverage)^2

The CRAP (Change Risk Anti-Patterns) score combines cyclomatic complexity with test coverage to identify risky code.

CRAP ScoreRisk LevelAction Required
< 5LowWell-tested, maintainable code
5-30MediumAcceptable but watch complexity
> 30HighNeeds tests or refactoring

Why CRAP Matters

  • High complexity + low coverage = danger: Code that's hard to understand AND untested is risky to modify
  • Complexity alone isn't enough: A complex method with 100% coverage is safer than a simple method with 0%
  • Focuses effort: Prioritize testing on complex code, not simple getters/setters

CRAP Score Examples

MethodComplexityCoverageCalculationCRAP
GetUserId()10%1 x (1 - 0)^21
ParseToken()5452%54 x (1 - 0.52)^212.4
ValidateForm()200%20 x (1 - 0)^220
ProcessOrder()4520%45 x (1 - 0.20)^228.8
ImportData()8010%80 x (1 - 0.10)^264.8

Coverage Collection Setup

coverage.runsettings

Create a coverage.runsettings file in your repository root. The OpenCover format is required for CRAP score calculation because it includes cyclomatic complexity metrics.

xml
<?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>

Key Configuration Options

OptionPurpose
FormatMust include opencover for complexity metrics
ExcludeExclude test/benchmark assemblies by pattern
ExcludeByAttributeSkip generated, obsolete, and explicitly excluded code (includes ExcludeFromCodeCoverageAttribute)
ExcludeByFileSkip source-generated files, Blazor components, and migrations
SkipAutoPropsDon't count auto-properties as branches

ReportGenerator Installation

Install ReportGenerator as a local tool for generating HTML reports with Risk Hotspots.

Add to .config/dotnet-tools.json

json
{  "version": 1,  "isRoot": true,  "tools": {    "dotnet-reportgenerator-globaltool": {      "version": "5.4.5",      "commands": ["reportgenerator"],      "rollForward": false    }  }}

Then restore:

bash
dotnet tool restore

Or Install Globally

bash
dotnet tool install --global dotnet-reportgenerator-globaltool

Collecting Coverage

Run Tests with Coverage Collection

bash
# Clean previous resultsrm -rf coverage/ TestResults/
# Run unit tests with coveragedotnet 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 ./TestResults

Generate HTML Report

bash
dotnet reportgenerator \  -reports:"TestResults/**/coverage.opencover.xml" \  -targetdir:"coverage" \  -reporttypes:"Html;TextSummary;MarkdownSummaryGithub"

Report Types

TypeDescriptionOutput
HtmlFull interactive reportcoverage/index.html
TextSummaryPlain text summarycoverage/Summary.txt
MarkdownSummaryGithubGitHub-compatible markdowncoverage/SummaryGithub.md
BadgesSVG badges for READMEcoverage/badge_*.svg
CoberturaMerged Cobertura XMLcoverage/Cobertura.xml

Reading the Report

Risk Hotspots Section

The HTML report includes a Risk Hotspots section showing methods sorted by complexity:

  • Cyclomatic Complexity: Number of independent paths through code (if/else, switch cases, loops)
  • NPath Complexity: Number of acyclic execution paths (exponential growth with nesting)
  • Crap Score: Calculated from complexity and coverage

Interpreting Results

Risk Hotspots─────────────Method                          Complexity  Coverage  Crap Score──────────────────────────────────────────────────────────────────DataImporter.ParseRecord()      54          52%       12.4AuthService.ValidateToken()     32          0%        32.0   ← HIGH RISKOrderProcessor.Calculate()      28          85%       1.3UserService.CreateUser()        15          100%      0.0

Action items:

  • ValidateToken() has CRAP > 30 with 0% coverage - test immediately or refactor
  • ParseRecord() is complex but has decent coverage - acceptable
  • CreateUser() and Calculate() are well-tested - safe to modify

Coverage Thresholds

Recommended Standards

Coverage TypeTargetAction
Line Coverage> 80%Good for most projects
Branch Coverage> 60%Catches conditional logic
CRAP Score< 30Maximum for new code

Configuring Thresholds

Create coverage.props in your repository:

xml
<Project>  <PropertyGroup>    <!-- Coverage thresholds for CI enforcement -->    <CoverageThresholdLine>80</CoverageThresholdLine>    <CoverageThresholdBranch>60</CoverageThresholdBranch>  </PropertyGroup></Project>

CI/CD Integration

GitHub Actions

yaml
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

yaml
- 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'

Quick Reference

One-Liner Commands

bash
# Full analysis workflowrm -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 summarycat 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

Project Standards

MetricNew CodeLegacy Code
Line Coverage80%+60%+ (improve gradually)
Branch Coverage60%+40%+ (improve gradually)
Maximum CRAP30Document exceptions
High-risk methodsMust have testsAdd tests before modifying

What Gets Excluded

The recommended coverage.runsettings excludes:

PatternReason
[*.Tests]*Test assemblies aren't production code
[*.Benchmark]*Benchmark projects
[*.Migrations]*Database migrations (generated)
GeneratedCodeAttributeSource generators
CompilerGeneratedAttributeCompiler-generated code
ExcludeFromCodeCoverageAttributeExplicit developer opt-out
*.g.cs, *.designer.csGenerated files
*.razor.g.csBlazor component generated code
*.razor.css.g.csBlazor CSS isolation generated code
**/Migrations/**/*EF Core migrations (auto-generated)
SkipAutoPropsAuto-properties (trivial branches)

When to Update Thresholds

Lower thresholds temporarily for:

  • Legacy codebases being modernized (document in README)
  • Generated code that can't be modified
  • Third-party wrapper code

Never lower thresholds for:

  • "It's too hard to test" - refactor instead
  • "We'll add tests later" - add them now
  • New features - should meet standards from the start

Additional Resources

來源與署名

來源:aaronontheweb/dotnet-skills位於skills/crap-analysis提交e426ed9

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架