MSBuild Task for PartCover

January 8, 2009 - 5 minute read -
nunit msbuild Automation code coverage

I continue to lament the dearth of option for Test Coverage in the .NET world.

In the Java world you have open source tools like Emma and Cobertura that are widely used and supported (and many more) as well as proprietary tools like Clover available.

.NET we have an open source NCover SF that requires you to do odd code instrumentation and is essentially dead it seams, another NCover which is proprietary and costs money and PartCover which is open source, but doesn't seem real active.

Don't get me wrong, NCover.org is a good option if you are willing to spend the money for it. But with a team of 30+ and a CI server, I'm not sure if I want to drop $10k on it. (NCover up until version 1.5.8 was Free Software (GPL) before it was closed. Who has the source and why haven't you forked it yet?)

]]>

If you're not willing to pay that basically leaves PartCover. But of course you want to integrate your code coverage with your automated build. There was no support for MSBuild out of the box, so I decided to build it.

Creating an MSBuild Task

I need to do 2 things to run PartCover:

  1. Register the native PartCover.CorDriver.dll
  2. Execute the PartCover.exe with the proper options

Register Native DLL

To see the details on how to register a native DLL using .NET code so my earlier post Register and Unregister COM DLL from .NET Code.

Execute PartCover

The MSBuild framework provides a ToolTask base class whose whole purpose is for executing external command line tools. I used this as the base of the task.

1. ToolName
First you override the ToolName property to return the name of the EXE to run. Nothing special here, it's just the executable name.

protected override string ToolName
{
    get { return "PartCover.exe"; }
}

2. Properties
Next to start build the task you go about defining all of the settings that a user will need to set to execute the task. You then create those as Properties on the class and they will be set by MSBuild. Start with the simple things that someone will need to pass to get the tool to execute properly. You can build from there for other properties. If possible give the properties sane defaults so that people don't have to override them in their build file.

// ...
/// <summary>
/// The application to execute to get the coverage results.
/// Generally this will be your unit testing exe.
/// </summary>
public string Target
{
    get { return _target; }
    set { _target = value; }
}

/// <summary>
/// The arguments to pass to the <see cref="Target"/> executable
/// </summary>
public string TargetArgs
{
    get { return _targetArgs; }
    set { _targetArgs = value; }
}

public string WorkingDirectory
{
    get { return _workingDirectory; }
    set { _workingDirectory = value; }
}
// ...

3. Command Arguments
Then you need to override string GenerateCommandLineCommands() method. The whole purpose of this method is to construct any command line parameters that need to be passed to the ToolName command using the Properties defined in the task.

protected override string GenerateCommandLineCommands()
{
    StringBuilder builder = new StringBuilder();
    AppendIfPresent(builder, "--target", Target);
    AppendIfPresent(builder, "--target-work-dir", WorkingDirectory);
    AppendIfPresent(builder, "--target-args", QuoteIfNeeded(TargetArgs));
    AppendIfPresent(builder, "--output", Output);
    AppendMultipleItemsTo(builder, "--include", Include);
    AppendMultipleItemsTo(builder, "--exclude", Exclude);
    Log.LogCommandLine(builder.ToString());
    return builder.ToString();
}

5. Execute
Finally, if you have anything special to do, you can override the Execute(). In this case, I wanted to handle the registering and de-registering of the Core.dll. Make sure that you call the base.Execute() method so that the TaskTarget can do the work that it needs to do.

public override bool Execute()
{
    string corDriverPath = Path.Combine(ToolPath, CorDriverName);
    Log.LogMessage("CoreDriver: {0}", corDriverPath);
    using (Registrar registrar = new Registrar(corDriverPath))
    {
        registrar.RegisterComDLL();
        return base.Execute();
    }
}

To see the whole thing, download the files at the bottom of this post.

How to Use PartCover with MSBuild

Now that you have a Custom task you need to create a Target in your MSBuild file to execute the task.

</p>
<!-- Register the PartCover.MSBuild.dll so the PartCover task is available -->
<UsingTask TaskName="PartCover.MSBuild.PartCover" AssemblyFile="$(LibDirectory)/PartCover/PartCover.MSBuild.dll" />
<!-- Setup a property so you can use it in your task -->
<ItemGroup>
    <TestAssemblies Include="src/ZorchedProj/bin/$(Configuration)/ZorchedProj.Tests.dll" />
</ItemGroup>
<!-- Create a Target to call the PartCover task -->
<Target Name="Test" DependsOnTargets="CoreBuild">
     <!-- Configure the task to execute -->
    <PartCover ToolPath="$(LibDirectory)/PartCover"
        Target="$(LibDirectory)NUnitnunit-console.exe"
        TargetArgs="%(TestAssemblies.FullPath) /xml=%(TestAssemblies.Filename).xml /labels /nologo /noshadow"
        WorkingDirectory="$(MSBuildProjectDirectory)"
        Output="partcover.xml"
        Include="[Zorched.*]*"
        Exclude="[*.Test]*"
    />
</Target>

Download the code: PartCover MSbuild.zip

Good luck and I hope someone else finds this useful.