用途
该技能用于在 .NET 项目中分析代码覆盖率与 CRAP 分数,识别高风险代码。适用场景包括:变更前评估代码质量与测试覆盖;识别需要重构或补充测试的高风险代码;为 .NET 项目配置覆盖率收集;基于风险对代码测试进行优先级排序;为 CI/CD 流水线建立覆盖率阈值。
什么是 CRAP
CRAP 分数 = 复杂度 × (1 - 覆盖率)²。它结合圈复杂度与测试覆盖率来识别风险代码。风险分级:< 5 为低风险,代码测试良好、可维护;5–30 为中等风险,可接受但需关注复杂度;> 30 为高风险,需要补测或重构。
高复杂度加低覆盖率意味着危险:既难理解又无测试的代码修改风险高。仅看复杂度不足:一个 100% 覆盖的复杂方法比 0% 覆盖的简单方法更安全。CRAP 有助于把精力集中在复杂代码的测试上,而非简单 getter/setter。
示例:GetUserId()(复杂度 1,覆盖 0%)CRAP 为 1;ParseToken()(54,52%)为 12.4;ValidateForm()(20,0%)为 20;ProcessOrder()(45,20%)为 28.8;ImportData()(80,10%)为 64.8。
覆盖率收集配置
在仓库根目录创建 coverage.runsettings。CRAP 分数计算要求 OpenCover 格式,因为该格式包含圈复杂度指标。配置中可设置:Format 需包含 opencover;Exclude 按模式排除测试/基准程序集;ExcludeByAttribute 跳过生成、过时及显式排除的代码(含 ExcludeFromCodeCoverageAttribute);ExcludeByFile 跳过源生成文件、Blazor 组件与迁移文件;IncludeTestAssembly 设为 false 排除测试项目;SkipAutoProps 不将自动属性计为分支。
ReportGenerator 安装
将 ReportGenerator 安装为本地工具,用于生成带 Risk Hotspots 的 HTML 报告。在 .config/dotnet-tools.json 中添加 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 运行测试并收集覆盖率,指定 --settings coverage.runsettings、--collect:"XPlat Code Coverage"、--results-directory ./TestResults。可先运行单元测试,再(可选)运行集成测试。之后用 dotnet reportgenerator 生成 HTML 报告,指定 -reports:"TestResults/**/coverage.opencover.xml"、-targetdir:"coverage",报告类型可包括 Html、TextSummary、MarkdownSummaryGithub。
报告类型包括:Html(完整交互报告,输出 coverage/index.html)、TextSummary(纯文本摘要)、MarkdownSummaryGithub(GitHub 兼容 Markdown)、Badges(README 用 SVG 徽章)、Cobertura(合并的 Cobertura XML)。
阅读报告
HTML 报告含 Risk Hotspots 区域,按复杂度排序列出方法,包含圈复杂度(代码中独立路径数量,如 if/else、switch 分支、循环)、NPath 复杂度(无环执行路径数量,嵌套时指数增长)、Crap Score(由复杂度与覆盖率计算)。示例中 DataImporter.ParseRecord() 复杂度 54、覆盖 52%、CRAP 12.4;AuthService.ValidateToken() 复杂度 32、覆盖 0%、CRAP 32.0(高风险);OrderProcessor.Calculate() 复杂度 28、覆盖 85%、CRAP 1.3;UserService.CreateUser() 复杂度 15、覆盖 100%、CRAP 0.0。行动项:CRAP > 30 且 0% 覆盖的方法应立即补测或重构;复杂但覆盖尚可的方法可接受;测试良好的方法可安全修改。
覆盖率阈值
推荐标准:行覆盖率 > 80%,分支覆盖率 > 60%,CRAP 分数 < 30(新代码上限)。可在仓库中创建 coverage.props 配置阈值:CoverageThresholdLine 设为 80,CoverageThresholdBranch 设为 60。
CI/CD 集成
提供 GitHub Actions 与 Azure Pipelines 示例配置。GitHub Actions 在 pull_request(main、dev)触发,步骤包括 checkout、setup-dotnet(9.0.x)、dotnet tool restore、带覆盖率运行测试、生成报告(Html、MarkdownSummaryGithub、Cobertura)、上传工件、通过 sticky-pull-request-comment 将摘要添加到 PR。Azure Pipelines 使用 DotNetCoreCLI@2 运行测试与自定义 reportgenerator 命令,并用 PublishCodeCoverageResults@2 发布 Cobertura 结果。
快速参考
完整分析工作流可在一条命令链中完成:清理、带覆盖率运行测试、生成报告。查看摘要可 cat coverage/Summary.txt;打开 HTML 报告可用 xdg-open(Linux)、open(macOS)、start(Windows)。
项目标准
新代码行覆盖率 80%+,旧代码 60%+(逐步改进);新代码分支覆盖率 60%+,旧代码 40%+(逐步改进);最大 CRAP 为 30,例外需记录;高风险方法必须测试,修改前先补测。
排除内容
推荐的 coverage.runsettings 排除:测试程序集([.Tests])、基准项目([.Benchmark])、数据库迁移([.Migrations])、源生成器(GeneratedCodeAttribute)、编译器生成代码(CompilerGeneratedAttribute)、开发者显式退出(ExcludeFromCodeCoverageAttribute)、生成文件(.g.cs、.designer.cs)、Blazor 组件生成代码(.razor.g.cs)、Blazor CSS 隔离生成代码(.razor.css.g.cs)、EF Core 迁移(/Migrations//*)、自动属性(SkipAutoProps)。
何时更新阈值
可临时降低阈值的情况:正在现代化改造的旧代码库(需在 README 记录)、无法修改的生成代码、第三方包装代码。绝不应降低阈值的情况:以“太难测试”为由(应重构)、以“以后再补测试”为由(应立即补)、新功能(应从开始就达标)。
附加资源
Coverlet 文档:https://github.com/coverlet-coverage/coverlet;ReportGenerator:https://github.com/danielpalme/ReportGenerator;CRAP 分数原始论文:http://www.artima.com/weblogs/viewpost.jsp?thread=215899。