Monday, 14 July 2014

Implementing DotCover from Powershell

We've just made the jump to JetBrains DotCover, and I'm reasonably impressed by the command line implementation. I think its worth mentioning that the Developers's are very happy with the Visual Studio integration, but that's not a part of the world I inhabit so I couldn't comment.

Supporting DotCover in our build pipeline was almost painless. The only part where I struggled was on the DotCover command line documentation. It took me a good while to realise that TargetArguments is where I specify the command line arguments used by the test-runner. In our case, the actual MSTest command line arguments.

Excuses aside, here's my Powershell invocation of JetBrains DotCover.

At present, we do not use the XML configuration files. But, when Developers start producing their own XML configurations within each visual studio solution, I shall implement that functionality later.
Instrumentation & Test

We automatically exclude the test assembly namespace from the coverage analysis.
  • We're not interested in how well our tests cover our tests.
  • i.e. com.product.domain.project.tests 
The scope of the coverage analysis is limited to only the project under examination.
  • Any unintentional coverage of other projects is discarded.
  • i.e. com.product.domain.project 
Individual coverage reports are merged into a domain report last
  • i.e. com.product.domain

$testRunner = "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe"
$testContainers = "D:\org\branch\namespace\domain\project.tests\bin\Output\Org.Namespace.Domain.Project.Tests.dll","D:\org\branch\namespace\domain\project.tests\bin\output\Org.Namespace.Domain.Project.Tests.dll"
foreach($test in $testContainers)
   $testAssembly = Get-Item $test
   $namespace = $testAssembly.BaseName -replace ".tests", ""
   $testName = $testAssembly.Name
   $testDirectory = (Split - Path $test - Parent)
   $testReport = "C:\temp\$testName.trx" & $coverageTool cover /TargetExecutable = "$testRunner" / Filters = "-:$namespace.Tests;+:class=$namespace*" /TargetArguments = "/testContainer:$test /resultsFile:$testReport" / Output = "C:\temp\$testName.dcvr" / LogFile ="C:\DotCover.log.txt"

Report Merging

The next steps merge up all the individual coverage reports generated by the unit test runs. This gives us our test coverage for the domain : com.product.domain.project.
$testReports = $testContainers | % {
   $name = Split-Path $_ -Leaf
   return ("c:\temp\{0}.dcvr" -f $name)

$testReportArgument = [String]::Join(";", $testReports)
& $coverageTool merge /Source="$testReportArgument" /Output="C:\temp\mergedSnapshots.dcvr"

Report Generation

The unified domain coverage reports can now be transformed into useful reports
HTML Report
Handy for quick review of coverage issues. We archive these reports in the build artefacts for the purposes of providing quick lookups. The reports persist until removed by the TFS retention policies.

& $coverageTool report /Source="C:\temp\mergedSnapshots.dcvr" /Output="C:\temp\mergedReport.html" /ReportType="HTML"

XML Report

Ideal format for the next stage where I extract the coverage metrics to complete the code quality assessment in the build pipeline.

& $coverageTool report /Source="C:\temp\mergedSnapshots.dcvr" /Output="C:\temp\mergedReport.xml" /ReportType="XML"

& $coverageTool report /Source="C:\temp\mergedSnapshots.dcvr" /Output="C:\temp\mergedReport.xml" /ReportType="XML"

Summary Extraction

The XML report provides a lot of information, but for the purposes of "Pass or Fail", I just need the combined coverage percentage. If it's falls below our agreed threshold, then the build must fail.

[xml]$coverageAnalysis = Get-Content "C:\temp\$mergedReport.xml"

$blocksCovered = $coverageAnalysis.Root.CoveredStatements;

$totalBlocks = $coverageAnalysis.Root.TotalStatements;

$totalCoverage = $coverageAnalysis.Root.CoveragePercent;