SonarQube与Azure DevOps代码覆盖率不一致问题求助
问题:Azure Pipelines中SonarQube覆盖率远低于Azure DevOps,特定项目未统计覆盖率
在Azure Pipelines中对.NET Core项目执行Sonar分析时,出现以下问题:
- Azure DevOps的代码覆盖率报告显示正常,但SonarQube上的覆盖率数值低很多
- 排查发现,测试项目引用的
Setup.csproj和Services.csproj对应的代码文件夹,在SonarQube中能看到代码,但没有生成覆盖率数据 - 使用Standalone scanner时SonarQube覆盖率正常,但切换为MSBuild扫描器时问题复现,且未设置任何显式的包含/排除规则
现有Pipeline配置
trigger: - sonar-analysis pool: vmImage: 'ubuntu-latest' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' steps: - checkout: self persistCredentials: true submodules: true - task: SonarQubePrepare@5 inputs: SonarQube: 'sonar-connection' scannerMode: 'MSBuild' projectKey: 'proj' projectName: 'proj' extraProperties: | sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/coverage.opencover.xml sonar.verbose=true sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx - task: UseDotNet@2 inputs: version: '6.x' - task: DotNetCoreCLI@2 inputs: command: 'build' projects: '**/**/*.csproj' arguments: '--verbosity n' - task: DotNetCoreCLI@2 displayName: 'dotnet test' inputs: command: 'test' projects: '**/*Tests/*.csproj' arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura,opencover' - task: PublishCodeCoverageResults@1 displayName: 'Publish code coverage report' inputs: codeCoverageTool: 'Cobertura' summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' - task: SonarQubeAnalyze@5 inputs: jdkversion: 'JAVA_HOME_11_X64' - task: SonarQubePublish@5 inputs: pollingTimeoutSec: '300'
测试项目Test.csproj内容
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> <ItemGroup> <PackageReference Include="AutoMapper" Version="12.0.1" /> <PackageReference Include="coverlet.msbuild" Version="6.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="6.0.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Moq" Version="4.18.2" /> <PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="coverlet.collector" Version="3.2.0"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\Core\Setup\Setup.csproj" /> <ProjectReference Include="..\..\Infrastructure\Services\Services.csproj" /> </ItemGroup> </Project>
原因分析
- MSBuild扫描器的项目绑定机制:MSBuild扫描器需要在构建阶段将SonarQube分析逻辑注入到每个项目的构建过程中。如果
Setup.csproj和Services.csproj在SonarQubePrepare任务之后的构建中没有被正确处理(比如路径匹配遗漏),SonarQube会将这些项目的代码视为"未被分析的代码",从而忽略对应的覆盖率数据。 - 覆盖率报告路径匹配问题:coverlet生成的opencover报告中,文件路径可能和SonarQube扫描到的文件路径不一致(比如相对路径/绝对路径差异、大小写差异),导致SonarQube无法匹配到对应代码的覆盖率数据。
- 测试任务的覆盖率收集范围:虽然测试项目引用了这两个项目,但coverlet可能没有正确收集到这两个项目的覆盖率数据,或者报告中未包含这些项目的信息。
解决步骤
1. 修正构建任务,确保所有目标项目被SonarQube注入分析逻辑
将DotNetCoreCLI@2的build任务改为使用solution变量,避免路径匹配遗漏:
- task: DotNetCoreCLI@2 inputs: command: 'build' projects: '$(solution)' arguments: '--configuration $(buildConfiguration) --verbosity n'
如果必须指定csproj,确保路径匹配包含目标项目:
projects: '**/*.csproj'
(原配置中的**/**/*.csproj是冗余的,可能导致部分项目被遗漏)
2. 确保覆盖率报告路径完全匹配
在SonarQubePrepare的extraProperties中添加路径匹配配置,同时显式指定测试任务的覆盖率输出路径:
# SonarQubePrepare任务的extraProperties新增配置 extraProperties: | sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/coverage.opencover.xml sonar.verbose=true sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx sonar.cs.coveragePaths=$(Build.SourcesDirectory)/**/*.cs
# 测试任务调整输出路径 - task: DotNetCoreCLI@2 displayName: 'dotnet test' inputs: command: 'test' projects: '**/*Tests/*.csproj' arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura,opencover DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Output=$(Agent.TempDirectory)/coverage'
3. 显式指定SonarQube的包含规则
在SonarQubePrepare的extraProperties中添加包含规则,确保目标项目代码被纳入分析:
extraProperties: | sonar.cs.opencover.reportsPaths=$(Agent.TempDirectory)/**/coverage.opencover.xml sonar.verbose=true sonar.cs.vstest.reportsPaths=$(Agent.TempDirectory)/**/*.trx sonar.inclusions=**/Core/Setup/**/*.cs,**/Infrastructure/Services/**/*.cs
4. 强制指定测试任务的覆盖率收集范围
在测试命令中显式指定要收集覆盖率的目标项目,避免coverlet遗漏:
arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura,opencover DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Include="[*]Core.Setup*,[*]Infrastructure.Services*"'
5. 验证SonarQube的项目分析状态
利用sonar.verbose=true开启详细日志,查看SonarQubeAnalyze任务的日志输出,确认Setup.csproj和Services.csproj是否被纳入分析。如果日志中没有相关记录,说明构建阶段未正确注入SonarQube逻辑,需要重新调整构建任务的项目范围。
内容的提问来源于stack exchange,提问作者Ajinkya Bapat
相关产品推荐
相关产品推荐

