MSBuild Task for PartCover

<![Rant[

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.


// ...

///

/// The application to execute to get the coverage results.
/// Generally this will be your unit testing exe.
///

public string Target
{
get { return _target; }
set { _target = value; }
}

///

/// The arguments to pass to the executable
///

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.







Target="$(LibDirectory)NUnitnunit-console.exe"
TargetArgs="%(TestAssemblies.FullPath) /xml=%(TestAssemblies.Filename).xml /labels /nologo /noshadow"
WorkingDirectory="$(MSBuildProjectDirectory)"
Output="partcover.xml"
Include="[Zorched.*]*"
Exclude="[*.Test]*"
/>

Download the code:
PartCover MSbuild.zip

Good luck and I hope someone else finds this useful.

16 thoughts on “MSBuild Task for PartCover”

  1. Not sure of it is possible, but if you quantify the quality you get from the commercial product per person, and compare it with what you lose from an inferior product, or none at all; what is the price difference in terms of what you end up paying in QA later on?

    If the value justifies the cost, buy it; there are bigger ways people can ruin the project that on easy stuff like this that we can automate.

  2. Hey,
    Thanks for your work on PartCover and MSBuild.
    I have noticed you have a fork of PartCover on GitHub, and I was wondering what differences it has with the original one. What is the best for me to use?

  3. @Callixte,
    Check out the github wiki. It’s got the information on the differences.
    Basically it’s got an MSBuild task and I made an option to output the coverage data in a format compatible with NCover 1.5.8 to make it easier to integrate with other tools.

  4. Hi,

    is it possible to use more than one TestAssembly in different Projects

    Thanks a lot
    -Frank

  5. Thanks Geoff,

    Ok, one last question. What’s the idea behind the working directory property? At which folder it must point?

    Thanks :-)
    -Frank

  6. Hm, ok … at last there was not the last question. know you an way to use Partcover under 64bit with your Buildtask?

  7. To add multiple includes/excludes, either:

    Exclude=”[nunit*]*;[log4net*]*”

    or


    Target=”$(LibDirectory)NUnitnunit-console.exe”
    TargetArgs=”%(TestAssemblies.FullPath) /xml=%(TestAssemblies.Filename).xml /labels /nologo /noshadow”
    WorkingDirectory=”$(MSBuildProjectDirectory)”
    Output=”partcover.xml”
    Exclude=”@(CoverageExclusions)”
    .
    .

  8. why did you fork Partcover? couldn’t u have contrib to Partcover project, keep changes all in one place, no.

  9. I tried to contribute a couple of patches, but they never got applied to the trunk. In fact that MSBuild patch was attached to the issue tracker on the sourceforge site.

  10. Geoff,
    are you actively maintaining the fork?
    I saw you fixed the “Add command line option to open report file – ID: 2445978″ partcover feature-request, too. Nice.
    I have done a little commanline-tool that parses the report.xml and fails if coverage drops below a certain point… I use that to include coverage-tests in my automated tests.
    I wanted to contribute to partcover, but seing as most requests there are being actively ignored I am not so sure anymore…

    Maybe you could add a wishlist to the wiki-page, see what happens :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>