Análise de Pontuação CRAP
Quando Usar Esta Skill
Use esta skill quando:
- Avaliar a qualidade do código e a cobertura de testes antes de alterações
- Identificar código de alto risco que precisa de refatoração ou testes
- Configurar a coleta de cobertura para um projeto .NET
- Priorizar qual código testar com base no risco
- Estabelecer limites de cobertura para pipelines CI/CD
O que é CRAP?
Pontuação CRAP = Complexidade x (1 - Cobertura)^2
A pontuação CRAP (Change Risk Anti-Patterns) combina a complexidade ciclomática com a cobertura de testes para identificar código arriscado.
| Pontuação CRAP | Nível de Risco | Ação Requerida |
|---|---|---|
| < 5 | Baixo | Código bem testado e sustentável |
| 5-30 | Médio | Aceitável, mas observe a complexidade |
| > 30 | Alto | Precisa de testes ou refatoração |
Por que o CRAP é Importante
- Alta complexidade + baixa cobertura = perigo: Código difícil de entender E não testado é arriscado de modificar
- Complexidade sozinha não é suficiente: Um método complexo com 100% de cobertura é mais seguro que um método simples com 0%
- Foca o esforço: Priorize testar código complexo, não getters/setters simples
Exemplos de Pontuação CRAP
| Método | Complexidade | Cobertura | Cálculo | 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 |
Configuração da Coleta de Cobertura
coverage.runsettings
Crie um arquivo coverage.runsettings na raiz do seu repositório. O formato OpenCover é obrigatório para o cálculo da pontuação CRAP porque inclui métricas de complexidade ciclomática.
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<!-- O formato OpenCover inclui complexidade ciclomática para pontuações CRAP -->
<Format>cobertura,opencover</Format>
<!-- Excluir assemblies de teste e benchmark -->
<Exclude>[*.Tests]*,[*.Benchmark]*,[*.Migrations]*</Exclude>
<!-- Excluir código gerado, membros obsoletos e exclusões explícitas -->
<ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute</ExcludeByAttribute>
<!-- Excluir arquivos gerados por fonte, código gerado pelo Blazor e migrações -->
<ExcludeByFile>**/obj/**/*,**/*.g.cs,**/*.designer.cs,**/*.razor.g.cs,**/*.razor.css.g.cs,**/Migrations/**/*</ExcludeByFile>
<!-- Excluir projetos de teste -->
<IncludeTestAssembly>false</IncludeTestAssembly>
<!-- Flags de otimização -->
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<SkipAutoProps>true</SkipAutoProps>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
Principais Opções de Configuração
| Opção | Propósito |
|---|---|
Format | Deve incluir opencover para métricas de complexidade |
Exclude | Excluir assemblies de teste/benchmark por padrão |
ExcludeByAttribute | Pular código gerado, obsoleto e explicitamente excluído (inclui ExcludeFromCodeCoverageAttribute) |
ExcludeByFile | Pular arquivos gerados por fonte, componentes Blazor e migrações |
SkipAutoProps | Não contar auto-propriedades como ramos |
Instalação do ReportGenerator
Instale o ReportGenerator como uma ferramenta local para gerar relatórios HTML com Risk Hotspots.
Adicionar ao .config/dotnet-tools.json
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "5.4.5",
"commands": ["reportgenerator"],
"rollForward": false
}
}
}
Depois restaure:
dotnet tool restore
Ou Instale Globalmente
dotnet tool install --global dotnet-reportgenerator-globaltool
Coletando Cobertura
Executar Testes com Coleta de Cobertura
# Limpar resultados anteriores
rm -rf coverage/ TestResults/
# Executar testes unitários com cobertura
dotnet test tests/MyApp.Tests.Unit \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
# Executar testes de integração (opcional, adiciona à cobertura)
dotnet test tests/MyApp.Tests.Integration \
--settings coverage.runsettings \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResults
Gerar Relatório HTML
dotnet reportgenerator \
-reports:"TestResults/**/coverage.opencover.xml" \
-targetdir:"coverage" \
-reporttypes:"Html;TextSummary;MarkdownSummaryGithub"
Tipos de Relatório
| Tipo | Descrição | Saída |
|---|---|---|
Html | Relatório interativo completo | coverage/index.html |
TextSummary | Resumo em texto simples | coverage/Summary.txt |
MarkdownSummaryGithub | Markdown compatível com GitHub | coverage/SummaryGithub.md |
Badges | Emblemas SVG para o README | coverage/badge_*.svg |
Cobertura | XML Cobertura mesclado | coverage/Cobertura.xml |
Lendo o Relatório
Seção Risk Hotspots
O relatório HTML inclui uma seção Risk Hotspots mostrando métodos ordenados por complexidade:
- Complexidade Ciclomática: Número de caminhos independentes através do código (if/else, casos switch, loops)
- Complexidade NPath: Número de caminhos de execução acíclicos (crescimento exponencial com aninhamento)
- Pontuação CRAP: Calculada a partir da complexidade e cobertura
Interpretando Resultados
Risk Hotspots
─────────────
Método Complexidade Cobertura Pontuação CRAP
────────────────────────────────────────────────────────────────────────
DataImporter.ParseRecord() 54 52% 12.4
AuthService.ValidateToken() 32 0% 32.0 ← ALTO RISCO
OrderProcessor.Calculate() 28 85% 1.3
UserService.CreateUser() 15 100% 0.0
Itens de ação:
ValidateToken()tem CRAP > 30 com 0% de cobertura - teste imediatamente ou refatoreParseRecord()é complexo mas tem cobertura decente - aceitávelCreateUser()eCalculate()estão bem testados - seguros para modificar
Limites de Cobertura
Padrões Recomendados
| Tipo de Cobertura | Alvo | Ação |
|---|---|---|
| Cobertura de Linha | > 80% | Bom para a maioria dos projetos |
| Cobertura de Ramo | > 60% | Captura lógica condicional |
| Pontuação CRAP | < 30 | Máximo para código novo |
Configurando Limites
Crie coverage.props no seu repositório:
<Project>
<PropertyGroup>
<!-- Limites de cobertura para imposição no CI -->
<CoverageThresholdLine>80</CoverageThresholdLine>
<CoverageThresholdBranch>60</CoverageThresholdBranch>
</PropertyGroup>
</Project>
Integração 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: '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'
Referência Rápida
Comandos de Uma Linha
# Fluxo de trabalho completo de análise
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"
# Visualizar resumo
cat coverage/Summary.txt
# Abrir relatório HTML (Linux)
xdg-open coverage/index.html
# Abrir relatório HTML (macOS)
open coverage/index.html
# Abrir relatório HTML (Windows)
start coverage/index.html
Padrões do Projeto
| Métrica | Código Novo | Código Legado |
|---|---|---|
| Cobertura de Linha | 80%+ | 60%+ (melhore gradualmente) |
| Cobertura de Ramo | 60%+ | 40%+ (melhore gradualmente) |
| CRAP Máximo | 30 | Documente exceções |
| Métodos de alto risco | Devem ter testes | Adicione testes antes de modificar |
O que é Excluído
O coverage.runsettings recomendado exclui:
| Padrão | Motivo |
|---|---|
[*.Tests]* | Assemblies de teste não são código de produção |
[*.Benchmark]* | Projetos de benchmark |
[*.Migrations]* | Migrações de banco de dados (geradas) |
GeneratedCodeAttribute | Geradores de fonte |
CompilerGeneratedAttribute | Código gerado pelo compilador |
ExcludeFromCodeCoverageAttribute | Exclusão explícita do desenvolvedor |
*.g.cs, *.designer.cs | Arquivos gerados |
*.razor.g.cs | Código gerado de componentes Blazor |
*.razor.css.g.cs | Código gerado de isolamento CSS do Blazor |
**/Migrations/**/* | Migrações do EF Core (geradas automaticamente) |
SkipAutoProps | Auto-propriedades (ramos triviais) |
Quando Atualizar os Limites
Abaixe os limites temporariamente para:
- Bases de código legado sendo modernizadas (documente no README)
- Código gerado que não pode ser modificado
- Código de wrapper de terceiros
Nunca abaixe os limites para:
- "É muito difícil de testar" - refatore em vez disso
- "Adicionaremos testes depois" - adicione agora
- Novos recursos - devem atender aos padrões desde o início
Recursos Adicionais
- Documentação do Coverlet: https://github.com/coverlet-coverage/coverlet
- ReportGenerator: https://github.com/danielpalme/ReportGenerator
- Artigo Original sobre Pontuação CRAP: http://www.artima.com/weblogs/viewpost.jsp?thread=215899