Tuesday, December 23, 2008

Running unit tests during builds in Visual Studio .NET

Hi there,

Some time ago I've attended a refactoring training provided by my employer. One of the interesting things there was that the project we worked on had unit tests hooked up to the build process. Now that I'm learning the .NET platform and the tools that come with it I've decided to give it a try and to integrate NUnit into the build process of a class library.

Visual Studio creates its projects in MSBuild format. That means that we can customize/extend the build process in a standard fashion as MSBuild is the standard tool (aside from NAnt) used to build .NET projects.

Here are the steps I took to enable such integration:

  1. Install NUnit (this procedure assumes the version 2.4.8 - note the version in ToolPath parameter of NUnit task).
  2. Install MSBuild community tasks (http://msbuildtasks.tigris.org/)
  3. Add the following lines to your class library project (.csproj) with tests (output file must be named "something.Tests.dll")

    <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
    <TestAssemblies Include="$(OutputPath)\$(AssemblyName).dll" />
    <Target Name="AfterBuild">
    <NUnit Assemblies="@(TestAssemblies)" ToolPath="$(ProgramFiles)\Nunit 2.4.8\bin\" XsltTransformFile="vsfmt.xslt" OutputXmlFile="$(OutputPath)\nunit-results.xml"/>

  4. Copy the following content into vsfmt.xslt in your test project path or somewhere else (remember to modify XsltTransformFile parameter if you store it somewhere else):

    <?xml version="1.0" encoding="UTF-8" ?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method='text'/>

    <xsl:template match="/">

    <xsl:template match="test-results">
    <xsl:apply-templates select="//test-case[failure]"/>

    <xsl:template match="test-case">
    <xsl:value-of select="substring-before(substring-after(failure/stack-trace, ' in '), ':line')"/>
    <xsl:value-of select="normalize-space(substring-after(substring-after(failure/stack-trace, ' in '), ':line '))"/>
    <xsl:text> : warning NU001: </xsl:text>
    <xsl:value-of select="@name"/><xsl:text>: </xsl:text>
    <xsl:value-of select="normalize-space(child::node()/message)" />
    <xsl:text disable-output-escaping='yes'>&#xD;&#xA;</xsl:text>

  5. Switch to VS - a dialog should appear that the project file has changed. Click "Reload".
  6. Rebuild solution - if there are any test cases they'll get executed as part of the build process.
  7. (optional) Add the following parameter to NUnit task if you don't want your unit tests to fail the build but rather to issue warnings:

    For example:

    <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
    <TestAssemblies Include="$(OutputPath)\$(AssemblyName).dll" />
    <Target Name="AfterBuild">
    <NUnit ContinueOnError="true" Assemblies="@(TestAssemblies)" ToolPath="$(ProgramFiles)\Nunit 2.4.8\bin\" XsltTransformFile="vsfmt.xslt" OutputXmlFile="$(OutputPath)\nunit-results.xml"/>

Remember that using this kind of integration with unit testing framework forces developers to create unit tests that are really fast. Nobody likes to wait for the build process to complete.

While working on this integration I've used the following pages as references:


Happy building!

No comments: