CI with Jenkins, MSBuild, Nuget and Git part 3

In the previous parts (part 1, part 2) of this series I described how to clean, download Nuget packages and build your solution using MSBuild. In this part I will explain you how to create a MSpec MSBuild target and a Code coverage MSBuild target.

MSpec is a testing framework which enables you to write living specifications. Using the MSpec console runner you can easily generate an html report. This report can later on be published using the Jenkins report plugin. By publishing this report we have some documentation on the specifications of the software available and because we did write the specifications using MSpec we also have unit tests in place. So that’s why I call it living documentation. :D

For generating the code coverage report we use the xml output of our MSpec tests. These reports will also be published using the Jenkins report plugin. To do so I use OpenCover and ReportGenerator.

All three packages are installed in my solution using Nuget. So the paths in my build script are based on the paths of my source/packages folders.

MSpec MSBuild target

First we add some variables/properties to our build script for easier access to the msbuild tools. In the MSpec target we first scan our directory for all assemblies containing ‘Specs’ in their name. So my Specification projects will be called something like this:

MyProject.Domain.Specs.csproj –> outputs MyProject.Domain.Specs.dll

MyProject.Service.Specs.csproj –> outputs MyProject.Service.Specs.dll

The last thing to mention about the MSpec MSBuild target is about the quotes needed. In MSBuild we can encode the code using &quot;. I encoded all needed quotes as you can see in the command definition.

Oh. The exclude for the Specs*.mm.dll is because I use ContinuousTests as plugin in my Visual Studio. It runs my specs when I save my files in Visual Studio. Because I also run the msbuild on my local machine I want to exclude these generated assemblies from the build task.

Code coverage MSBuild target

For generating code coverage we also add some variables/properties to our MSBuild script. Again I scan for all the spec assemblies which should be used to generate the coverage. We use MSBuild as target to execute the Specifications and we exclude all Spec assemblies from the coverage report using the $(OpenCoverFilter) property. Based on the xml output of OpenCover we use Report Generator to generate an html report and a xml summary with the coverage results of our solution.

1234567891011121314151617181920212223242526272829303132

<PropertyGroup><!-- OpenCover --><!-- The tools path for OpenCover --><OpenCoverPath>$(Packages)\OpenCover.4.5.1403</OpenCoverPath><OpenCoverExe>OpenCover.Console.exe</OpenCoverExe><OpenCoverFilter>-[*Specs*]* +[*]*</OpenCoverFilter><ReportGeneratorPath>$(Packages)\ReportGenerator.1.8.1.0</ReportGeneratorPath><ReportGeneratorExe>ReportGenerator.exe</ReportGeneratorExe><OpenCoverOutputFile>$(ReportsPath)\coverage-output.xml</OpenCoverOutputFile><CoverageReport>$(ReportsPath)\coverage</CoverageReport><ReportGeneratorSummary>$(ReportsPath)\coverage-summary.xml</ReportGeneratorSummary></PropertyGroup><TargetName="CodeCoverage"DependsOnTargets="Clean;LoadNuGetPackages;Compile"><CreateItemInclude="**\Bin\Debug\*Specs*.dll"Exclude="**\Bin\$(Configuration)\*Specs*.mm.dll"><OutputTaskParameter="Include"ItemName="SpecsAssemblies" /></CreateItem><PropertyGroup><OpenCoverCommand>&quot;$(OpenCoverPath)\$(OpenCoverExe)&quot; -register:user &quot;-target:&quot;$(MSpecPath)\$(MSpecExe)&quot;&quot; &quot;-targetargs:&quot;@(SpecsAssemblies, '&quot; &quot;')&quot;&quot; &quot;-filter:$(OpenCoverFilter)&quot; &quot;-output:$(OpenCoverOutputFile)&quot;</OpenCoverCommand><ReportGeneratorCommand>&quot;$(ReportGeneratorPath)\$(ReportGeneratorExe)&quot; &quot;-reports:$(OpenCoverOutputFile)&quot; &quot;-targetdir:$(CoverageReport)&quot; &quot;-reporttypes:html;xml&quot;</ReportGeneratorCommand></PropertyGroup><MessageImportance="high"Text="Running code coverage with this command: $(OpenCoverCommand)"/><ExecCommand="$(OpenCoverCommand)" /><MessageImportance="high"Text="Generate report with this command: $(ReportGeneratorCommand)"/><ExecCommand="$(ReportGeneratorCommand)" /><!-- Report Generator has no way to name the output file so rename it by copying and deleting the original file --><CopySourceFiles="$(CoverageReport)\Summary.xml"DestinationFiles="$(ReportGeneratorSummary)"></Copy><DeleteFiles="$(CoverageReport)\Summary.xml"></Delete></Target>

Of course we could have chosen to put the scanning for assemblies outside the targets so both targets can use the same output. However I choose to make them dedicated to the target, because I want my targets to be completely independent. So when I choose to change the included assemblies for my coverage this doesn’t influence my other target. However you are free to share the scanning part in both targets.

So how does our complete MSBuild script look now. To execute the targets we can use the /t:Specs or the /t:Coverage command line parameter. Or to do both /t:Specs;Coverage. You could add some if statements, to be able to choose the target, to the batch file from part 1.