Community코딩 & 개발github.com

CRAP 분석

코드 커버리지와 CRAP(Change Risk Anti-Patterns) 점수를 분석하여 고위험 코드를 식별합니다. ReportGenerator의 Risk Hotspots를 통해 순환 복잡도와 테스트되지 않은 코드 경로를 확인할 수 있습니다.

CRAP 분석란 무엇인가요?

이 스킬은 .NET 프로젝트에서 코드 품질과 테스트 커버리지를 평가하고, 변경 시 위험이 높은 코드를 식별하는 데 사용됩니다. CRAP 점수는 순환 복잡도와 테스트 커버리지를 결합하여 유지보수 위험을 정량화합니다. OpenCover 형식의 커버리지 데이터를 수집하고 ReportGenerator를 통해 HTML 보고서를 생성하며, Risk Hotspots 섹션에서 복잡도, 커버리지, CRAP 점수를 한눈에 볼 수 있습니다. GitHub Actions, Azure Pipelines와 같은 CI/CD 파이프라인에 통합하여 코드 변경 시 자동으로 품질 게이트를 적용할 수 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/crap-analysis

Installed? Explore more 코딩 & 개발 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

CRAP 점수 분석

이 스킬을 사용해야 할 때

다음 상황에서 이 스킬을 사용하세요:

  • 변경 전 코드 품질과 테스트 커버리지 평가
  • 리팩토링 또는 테스트가 필요한 고위험 코드 식별
  • .NET 프로젝트의 커버리지 수집 설정
  • 위험 기반 테스트 우선순위 지정
  • CI/CD 파이프라인에 커버리지 임계값 설정

CRAP이란?

CRAP 점수 = 복잡도 x (1 - 커버리지)^2

CRAP(Change Risk Anti-Patterns) 점수는 순환 복잡도와 테스트 커버리지를 결합하여 위험한 코드를 식별합니다.

CRAP 점수위험 수준필요한 조치
< 5낮음잘 테스트되고 유지보수 가능한 코드
5-30보통수용 가능하지만 복잡도 주시 필요
> 30높음테스트 또는 리팩토링 필요

CRAP이 중요한 이유

  • 높은 복잡도 + 낮은 커버리지 = 위험: 이해하기 어렵고 테스트되지 않은 코드는 수정 시 위험
  • 복잡도만으로는 부족: 100% 커버리지의 복잡한 메서드는 0% 커버리지의 간단한 메서드보다 안전
  • 노력 집중: 단순한 getter/setter가 아닌 복잡한 코드에 테스트 우선순위 지정

CRAP 점수 예시

메서드복잡도커버리지계산식CRAP
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.runsettings

리포지토리 루트에 coverage.runsettings 파일을 생성하세요. CRAP 점수 계산에는 OpenCover 형식이 필수입니다. OpenCover는 순환 복잡도 메트릭을 포함하기 때문입니다.

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat code coverage">
        <Configuration>
          <!-- OpenCover 형식은 CRAP 점수를 위한 순환 복잡도를 포함 -->
          <Format>cobertura,opencover</Format>

          <!-- 테스트 및 벤치마크 어셈블리 제외 -->
          <Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude>

          <!-- 생성된 코드, 사용되지 않는 멤버, 명시적 제외 대상 제외 -->
          <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>

          <!-- 소스 생성 파일, Blazor 생성 코드, 마이그레이션 제외 -->
          <ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile>

          <!-- 테스트 프로젝트 제외 -->
          <IncludeTestAssembly>false</IncludeTestAssembly>

          <!-- 최적화 플래그 -->
          <SingleHit>false</SingleHit>
          <UseSourceLink>true</UseSourceLink>
          <SkipAutoProps>true</SkipAutoProps>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

주요 구성 옵션

옵션목적
Format복잡도 메트릭을 위해 opencover 포함 필수
Exclude패턴으로 테스트/벤치마크 어셈블리 제외
ExcludeByAttribute생성된 코드, 사용되지 않는 코드, 명시적 제외 대상 건너뛰기 (ExcludeFromCodeCoverageAttribute 포함)
ExcludeByFile소스 생성 파일, Blazor 컴포넌트, 마이그레이션 제외
SkipAutoProps자동 속성을 분기로 계산하지 않음

ReportGenerator 설치

Risk Hotspots가 포함된 HTML 보고서를 생성하려면 ReportGenerator를 로컬 도구로 설치하세요.

.config/dotnet-tools.json에 추가

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

그런 다음 복원:

dotnet tool restore

또는 전역 설치

dotnet tool install --global dotnet-reportgenerator-globaltool

커버리지 수집

테스트 실행 및 커버리지 수집

# 이전 결과 정리
rm -rf coverage/ TestResults/

# 단위 테스트 실행 (커버리지 포함)
dotnet test tests/MyApp.Tests.Unit \
  --settings coverage.runsettings \
  --collect:"XPlat Code Coverage" \
  --results-directory ./TestResults

# 통합 테스트 실행 (선택 사항, 커버리지에 추가)
dotnet test tests/MyApp.Tests.Integration \
  --settings coverage.runsettings \
  --collect:"XPlat Code Coverage" \
  --results-directory ./TestResults

HTML 보고서 생성

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

보고서 유형

유형설명출력
Html전체 대화형 보고서coverage/index.html
TextSummary일반 텍스트 요약coverage/Summary.txt
MarkdownSummaryGithubGitHub 호환 마크다운coverage/SummaryGithub.md
BadgesREADME용 SVG 뱃지coverage/badge_*.svg
Cobertura병합된 Cobertura XMLcoverage/Cobertura.xml

보고서 읽기

Risk Hotspots 섹션

HTML 보고서에는 복잡도 순으로 정렬된 메서드를 보여주는 Risk Hotspots 섹션이 포함됩니다:

  • 순환 복잡도(Cyclomatic Complexity): 코드를 통한 독립적인 경로 수 (if/else, switch case, 루프)
  • NPath 복잡도: 비순환 실행 경로 수 (중첩에 따라 기하급수적으로 증가)
  • CRAP 점수: 복잡도와 커버리지로 계산

결과 해석

Risk Hotspots
─────────────
메서드                          복잡도  커버리지  CRAP 점수
───────────────────────────────────────────────────────────────
DataImporter.ParseRecord()      54      52%       12.4
AuthService.ValidateToken()     32      0%        32.0   ← 높은 위험
OrderProcessor.Calculate()      28      85%       1.3
UserService.CreateUser()        15      100%      0.0

조치 항목:

  • ValidateToken() CRAP > 30에 0% 커버리지 - 즉시 테스트 또는 리팩토링
  • ParseRecord()는 복잡하지만 적절한 커버리지 있음 - 수용 가능
  • CreateUser()와 Calculate()는 잘 테스트됨 - 수정 안전

커버리지 임계값

권장 기준

커버리지 유형목표조치
라인 커버리지> 80%대부분의 프로젝트에 적합
분기 커버리지> 60%조건부 로직 검출
CRAP 점수< 30새 코드의 최대값

임계값 구성

리포지토리에 coverage.props를 생성하세요:

<Project>
  <PropertyGroup>
    <!-- CI 강제 적용을 위한 커버리지 임계값 -->
    <CoverageThresholdLine>80</CoverageThresholdLine>
    <CoverageThresholdBranch>60</CoverageThresholdBranch>
  </PropertyGroup>
</Project>

CI/CD 통합

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: '커버리지로 테스트 실행'
  inputs:
    command: 'test'
    arguments: '--settings coverage.runsettings --collect:"XPlat Code Coverage" --results-directory $(Build.SourcesDirectory)/TestResults'

- task: DotNetCoreCLI@2
  displayName: '커버리지 보고서 생성'
  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: '커버리지 게시'
  inputs:
    codeCoverageTool: 'Cobertura'
    summaryFileLocation: '$(Build.SourcesDirectory)/coverage/Cobertura.xml'

빠른 참조

한 줄 명령어

# 전체 분석 워크플로
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"

# 요약 보기
cat coverage/Summary.txt

# HTML 보고서 열기 (Linux)
xdg-open coverage/index.html

# HTML 보고서 열기 (macOS)
open coverage/index.html

# HTML 보고서 열기 (Windows)
start coverage/index.html

프로젝트 기준

메트릭새 코드레거시 코드
라인 커버리지80%+60%+ (점진적 개선)
분기 커버리지60%+40%+ (점진적 개선)
최대 CRAP30예외 사항 문서화
고위험 메서드테스트 필수수정 전 테스트 추가

제외 대상

권장 coverage.runsettings에서 제외되는 항목:

패턴이유
[*.Tests]*테스트 어셈블리는 프로덕션 코드가 아님
[*.Benchmark]*벤치마크 프로젝트
[*.Migrations]*데이터베이스 마이그레이션 (생성됨)
GeneratedCodeAttribute소스 생성기
CompilerGeneratedAttribute컴파일러 생성 코드
ExcludeFromCodeCoverageAttribute개발자의 명시적 제외
*.g.cs, *.designer.cs생성된 파일
*.razor.g.csBlazor 컴포넌트 생성 코드
*.razor.css.g.csBlazor CSS 격리 생성 코드
**/Migrations/**/*EF Core 마이그레이션 (자동 생성)
SkipAutoProps자동 속성 (간단한 분기)

임계값 업데이트 시기

일시적으로 임계값 낮추기:

  • 현대화 중인 레거시 코드베이스 (README에 문서화)
  • 수정할 수 없는 생성된 코드
  • 서드파티 래퍼 코드

임계값을 절대 낮추지 말아야 할 때:

  • "테스트하기 너무 어렵다" - 대신 리팩토링
  • "나중에 테스트를 추가하겠다" - 지금 추가
  • 새로운 기능 - 처음부터 기준을 충족해야 함

추가 자료

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

csharp-concurrency-patterns

Choosing the right concurrency abstraction in .NET - from async/await for I/O to Channels for producer/consumer to Akka.NET for stateful entity management. Avoid locks and manual synchronization unless absolutely necessary.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

관련 스킬