You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OpenCover与Vstest无法发布代码覆盖率报告至Sonar Cloud

问题:OpenCover生成的代码覆盖率报告无法在SonarCloud仪表盘展示

基于.NET Framework应用,通过Azure DevOps YAML流水线使用OpenCover.Console.exe和vstest.console.exe生成代码覆盖率报告,已确认CoverageResults.xml生成成功且SonarCloud分析任务已拾取该文件,但SonarCloud仪表盘始终不显示覆盖率数据。流水线关键配置如下:

steps:
  - checkout: self
    clean: true

  - task: NuGetToolInstaller@1
    displayName: 'Use NuGet 4.x'
    inputs:
     versionSpec: 4.x
     checkLatest: true

# Nuget restore for Service and presentation layer
  - task: NuGetCommand@2
    displayName: 'NuGet restore for service'
    inputs:
      restoreSolution: '${{ parameters.servicenugetSolution }}'
      feedsToUse: config
      nugetConfigPath: ${{ parameters.ServicenugetPath }}
    condition: and(succeeded(), or(eq('${{ parameters.Service }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )

  - task: NuGetCommand@2
    displayName: 'NuGet restore for Presentation'
    inputs:
      restoreSolution: ${{ parameters.presentationSolution }}
      feedsToUse: config
      nugetConfigPath: ${{ parameters.presentationnugetPath }}
    condition: and(succeeded(), or(eq('${{ parameters.Presentation }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )
     
  - task: NuGetCommand@2
    displayName: 'NuGet restore for Solution'
    inputs:
       restoreSolution: '**/*.sln'
       feedsToUse: config
       nugetConfigPath: ${{ parameters.presentationnugetPath }}
    condition: and(succeeded(), eq('${{ parameters.nugetRestoreAllSolutions }}', 'yes'))

# Sonar cloud for Service and presentation layer
  - task: SonarSource.sonarcloud.14d9cde6-c1da-4d55-aa01-2965cd301255.SonarCloudPrepare@1
    displayName: 'Prepare analysis on SonarCloud'
    condition: and(succeeded(), ne('${{ parameters.SonarProjectKey }}', ''), or(eq('${{ parameters.Presentation }}', 'true'), eq('${{ parameters.Service }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')))
    inputs:
      SonarCloud: SonarCloud
      organization: msc
      projectKey: ${{ parameters.SonarProjectKey }}
      projectName: ${{ parameters.SonarProjectName }}
      extraProperties: |
        sonar.exclusions=${{ parameters.SonarExclusions }}
        sonar.dotnet.excludeTestProjects=true
        sonar.cs.opencover.reportsPaths=$(Build.SourcesDirectory)/CoverageResults.xml

# Service Solution Build
  - task: VSBuild@1
    displayName: 'Build solution ${{ parameters.serviceSolution }}'
    inputs:
      solution: ${{ parameters.serviceSolution }}
      msbuildArgs: '/p:OutDir=$(Build.BinariesDirectory)/Service /p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true'
      platform: '$(BuildPlatform)'
      configuration: '$(BuildConfiguration)'
      clean: true
      msbuildArchitecture: x64
      logProjectEvents: false
      maximumCpuCount: true
      vsVersion: ${{ parameters.VsVersion }}
    condition: and(succeeded(), or(eq('${{ parameters.Service }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )

# Presentation Solution Build
  - task: VSBuild@1
    displayName: 'Build solution ${{ parameters.presentationSolution }}'
    inputs:
      solution: ${{ parameters.presentationSolution }}
      msbuildArgs: '/p:OutDir=$(Build.BinariesDirectory)/Presentation'
      platform: '$(BuildPlatform)'
      configuration: '$(BuildConfiguration)'
      clean: true
      msbuildArchitecture: x64
      logProjectEvents: false
      maximumCpuCount: true
      vsVersion: ${{ parameters.VsVersion }}
    condition: and(succeeded(), or(eq('${{ parameters.Presentation }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )

  - task: PowerShell@2
    displayName: 'Execute Code Coverage'
    inputs:
      targetType: 'inline'
      script: |
        get-location

        Echo "LocationPath:" $(Build.BinariesDirectory)
        Set-Location -Path $(Build.BinariesDirectory)

        $getSrcdirectory = "$(Build.SourcesDirectory)"

        $OpenCover = "E:\Software\opencover.4.7.1221\OpenCover.Console.exe"
        $ReportGenerator = "$(Build.BinariesDirectory)\reportgenerator.exe"

        &$OpenCover -register:user -target:"vstest.console.exe" -targetargs:".\service\SampleWork.Api.Test.dll" -output:"$($getSrcdirectory)\CoverageResults.xml"
 
# Run Code Analysis &Publish Quality Gate Result
  - task: SonarSource.sonarcloud.ce096e50-6155-4de8-8800-4221aaeed4a1.SonarCloudAnalyze@1
    displayName: 'Run Code Analysis'
    condition: and(succeeded(), or(eq('${{ parameters.Presentation }}', 'true'), eq('${{ parameters.Service }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )

排查与解决步骤

1. 修复OpenCover路径兼容性问题

OpenCover默认生成的路径格式可能和SonarCloud的路径识别逻辑不匹配,修改PowerShell脚本中的OpenCover命令,添加-oldstyle参数:

&$OpenCover -register:user -oldstyle -target:"vstest.console.exe" -targetargs:".\service\SampleWork.Api.Test.dll" -output:"$($getSrcdirectory)\CoverageResults.xml"

-oldstyle参数会强制OpenCover使用旧格式的文件路径,确保SonarCloud能正确关联覆盖率数据到源代码文件。

2. 补充SonarCloudPublish任务

当前流水线缺少提交分析结果到SonarCloud服务器的步骤,在SonarCloudAnalyze任务后添加:

- task: SonarSource.sonarcloud.38b27399-a642-40af-bb7d-9971f69712e8.SonarCloudPublish@1
  displayName: 'Publish Quality Gate Result'
  condition: and(succeeded(), or(eq('${{ parameters.Presentation }}', 'true'), eq('${{ parameters.Service }}', 'true'), eq('${{ parameters.ServiceandPresentation }}', 'true')) )

没有这个任务,SonarCloud仅完成本地分析,不会将覆盖率数据同步到仪表盘。

3. 验证编译配置一致性

确保测试项目与被测项目的**编译配置(Debug/Release)、平台(x64/x86)**完全一致。VSBuild任务中指定的configuration和platform参数,必须和vstest执行的测试dll编译参数匹配,否则OpenCover无法正确捕获覆盖率。

4. 检查SonarCloud排除规则

确认sonar.exclusions参数没有误将被测项目的源码排除在外。如果被测项目被排除,SonarCloud不会展示其覆盖率数据。

5. 开启SonarCloud调试日志排查

在SonarCloudPrepare任务的extraProperties中添加sonar.verbose=true,查看SonarCloudAnalyze任务的日志,确认是否有"Parsing OpenCover report"的成功日志,以及是否存在路径匹配失败的警告信息。

6. 确保覆盖所有测试项目

如果存在多个测试项目,需在OpenCover的-targetargs中全部列出,避免遗漏覆盖率数据:

-targetargs:".\service\SampleWork.Api.Test.dll .\service\SampleWork.Business.Test.dll"

内容的提问来源于stack exchange,提问作者sudalai muthukumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:32:09