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 从公开仓库中收录这些内容。

举报或申请下架