title: Optimizing C# code coverage collection and publishing in Azure DevOps Server 2020
body: <p>We use the following command line (more or less) to collect C# code coverage in our pipeline:</p>
  <pre><code>dotnet test --no-build -l trx -r TheResultsDir --collect &quot;Code coverage&quot; -s  CodeCoverage.runsettings
  </code></pre>
  <p>(We actually use the build-in <code>DotNetCoreCLI@2</code> task for that)</p>
  <p>This produces a bunch of <code>.coverage</code> files. We want to do 2 things with them:</p>
  <ol>
  <li>Send to our SonarQube server</li>
  <li>Publish on the build itself using <code>PublishCodeCoverageResults@1</code> task.</li>
  </ol>
  <p>As it turns out (surprise, surprise), the produced <code>.coverage</code> files are only understood by VS IDE.</p>
  <p>As usual - Internet to the rescue. We figured out that:</p>
  <ol>
  <li>Using the <a href="https://www.nuget.org/packages/Microsoft.CodeCoverage/" rel="nofollow noreferrer">Microsoft.CodeCoverage</a> tool we can convert the <code>.coverage</code> files to <code>.xml</code> which are understood by SonarQube, but not the <code>PublishCodeCoverageResults@1</code> task.</li>
  <li>Using the <a href="https://www.nuget.org/packages/dotnet-reportgenerator-globaltool" rel="nofollow noreferrer">reportgenerator</a> tool we can convert the <code>.xml</code> files from (1) to the Cobertura format understood by the <code>PublishCodeCoverageResults@1</code> task</li>
  </ol>
  <p>And that is what we do and it is fine for small projects. However, now we introduce code coverage to our big monolithic application (sigh, helas - all is true) and the timings are awful. For one solution (out of several):</p>
  <ol>
  <li>Conversion from <code>.coverage</code> to <code>.xml</code> takes about 20 minutes.</li>
  <li>Conversion from <code>.xml</code> to Cobertura - 2h 33m.</li>
  </ol>
  <p>So, this is really bad.</p>
  <p><strong>Is there a better solution if I want both send to SQ and publish to the build?</strong></p>
  <p>Here is the actual code we use:</p>
  <p><strong>From <code>.coverage</code> to <code>.xml</code></strong></p>
  <pre><code>CodeCoverage.exe analyze /output:CoverageResult.xml CoverageResult.coverage
  </code></pre>
  <p>(there are several <code>.coverage</code> files, so the command is applied on each one)</p>
  <p><strong>From <code>.xml</code> to Cobertura</strong></p>
  <pre><code>reportgenerator.exe -reports:CoverageResults\*.xml -targetdir:CoberturaReport -reporttypes:Cobertura
  </code></pre>
  <p><strong>Publishing to build</strong></p>
  <pre><code>  - task: PublishCodeCoverageResults@1
      displayName: Publish Coverage Results
      inputs:
        codeCoverageTool: Cobertura
        summaryFileLocation: CoberturaReport/Cobertura.xml
        failIfCoverageEmpty: true
  </code></pre>
  <p>Publishing to SonarQube is done with the standard SQ tasks:</p>
  <ol>
  <li>Prepare SQ Analysis</li>
  <li>Run SQ Analysis</li>
  <li>Publish SQ Analysis</li>
  </ol>
  <p>The prepare task is:</p>
  <pre><code>  - task: SonarQubePrepare@4
      displayName: Prepare CI SQ Analysis
      inputs:
        SonarQube: SonarQube
        scannerMode: MSBuild
        projectKey: $(SonarQubeProjectKey)
        projectName: $(SonarQubeProjectName)
        projectVersion: $(SonarQubeProjectVersion)
        extraProperties: |
          sonar.cs.vscoveragexml.reportsPaths=$(Common.TestResultsDirectory)\vstest-coverage\*.xml
          sonar.cs.nunit.reportsPaths=$(Common.TestResultsDirectory)\tests\*.TestResult.xml
          sonar.inclusions=**/*.cs
          sonar.branch.name=$(SonarQubeSourceBranch)
          sonar.scm.disabled=true
  </code></pre>