Click here to Skip to main content
15,884,838 members
Articles / Programming Languages / XML
Tip/Trick

Adding unit testing and code coverage to your Nant scripts

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
19 Sep 2014CPOL3 min read 11.7K   1   1
Adding unit testing and code coverage to your Nant scripts

Introduction

If you have used Nant previously to build your applications, then you may want to consider adding unit-tests and code-coverage to your script. If you have written unit-tests for your application then it is perfectly reasonable to want these to be executed as part of your build process. To ensure your unit-testing is of sufficient quality, you may also want to add code-coverage to your build process to ensure that you have adequate code-coverage as specified by your unit-tests.

Background

It is assumed that the reader is familiar with Nant syntax. This article uses NUnit as the unit-testing tool, and NCover as the code-coverage tool. Familiarity with both of these tools is assumed.

Although I created the Nant script to build a Visual Studio application, the concepts described here can be equally well applied to other languages / environments.

Solution structure

The way I have organised my solution is to have a unit-testing project for each project. So if my solution contains a project called BusinessTier then it will also contain a corresponding project called BusinessTier.Tests which contains the unit-tests that exercise the functionality within BusinessTier.

In the solution used for the article there are three projects and therefore three corresponding unit-testing projects (giving a total of six projects in the solution).

  • BusinessTier is unit-tested by BusinessTier.Tests
  • Common is unit-tested by Common.Tests
  • DataTier is unit-tested by DataTier.Tests

Adding unit-testing to your Nant build script

In the Nant script below there is an Nant target called ExecuteUnitTests defined. This is the main wrapper process which sets up the necessary properties which are then used by the worker target called UnitTestAssembly.

XML
<target name="ExecuteUnitTests">
  <echo message="Task execution started at : ${script::format-to-string(datetime::now())}" />

  <property name="nunit.console.file" value="C:\nunit\bin\nunit-console-x86.exe" />
  <property name="verbose" value="true" />

  <!--Execute the unit tests against the BusinessTier assembly-->
  <property name="unit.tests.working.dir" value="${build.dir}\BusinessTier.Tests\bin\Release\" />
  <property name="unit.tests.assembly.name" value="BusinessTier.Tests.dll" />
  <call target="UnitTestAssembly" />

  <!--Execute the unit tests against the Common assembly-->
  <property name="unit.tests.working.dir" value="${build.dir}\Common.Tests\bin\Release\" />
  <property name="unit.tests.assembly.name" value="Common.Tests.dll" />
  <call target="UnitTestAssembly" />

  <!--Execute the unit tests against the DataTier assembly-->
  <property name="unit.tests.working.dir" value="${build.dir}\DataTier.Tests\bin\Release\" />
  <property name="unit.tests.assembly.name" value="DataTier.Tests.dll" />
  <call target="UnitTestAssembly" />

  <echo message="Task execution finished at : ${script::format-to-string(datetime::now())}" />
</target>

<target name="UnitTestAssembly">
  <echo message="Task execution started at : ${script::format-to-string(datetime::now())}" />

  <exec program="${nunit.console.file}" failonerror="true" verbose="${verbose}"
        workingdir="${unit.tests.working.dir}"
        commandline="${unit.tests.assembly.name} /xml:TestResults.xml /nologo"/>

  <echo message="Task execution finished at : ${script::format-to-string(datetime::now())}" />
</target>

In the script above, the three assemblies are unit-tested in sequence by the target ExecuteUnitTests. This sets up the assembly name and location for each of the assemblies (BusinessTier.Tests, Common.Tests and DataTier.Tests). It then calls the worker target UnitTestAssembly which invokes NUnit.Console and exercises the unit-tests within the assembly.

Adding code-coverage to your Nant build script

In the Nant script below there is an Nant target called ExecuteTestCoverage defined. This is the main wrapper process which sets up the necessary properties which are then used by the worker target called AssemblyCoverage.

XML
<target name="ExecuteTestCoverage">
    <echo message="Task execution started at : ${script::format-to-string(datetime::now())}" />

    <property name="build.dir" value="${directory::get-current-directory()}" />
    <property name="nunit.console.file" value="C:\nunit\bin\nunit-console-x86.exe" />
    <property name="ncover.console.file" value="C:\ncover\ncover.console.exe" />
    
    <!-- Execute code coverage for the BusinessTier assembly -->
    <property name="tests.dir"	value="BusinessTier.Tests\bin\Debug\" />
    <property name="assembly.to.test"	value="BusinessTier" />
    <property name="assembly.test.fixture"	value="BusinessTier.Tests.dll" />
    <property name="test.fixture.output.file"  value="${path::change-extension(assembly.test.fixture, '.xml')}" />
    <call target="AssemblyCoverage" />

    <!-- Execute code coverage for the Common assembly -->
    <property name="tests.dir"	value="Common.Tests\bin\Debug\" />
    <property name="assembly.to.test"	value="Common" />
    <property name="assembly.test.fixture"	value="Common.Tests.dll" />
    <property name="test.fixture.output.file"  value="${path::change-extension(assembly.test.fixture, '.xml')}" />
    <call target="AssemblyCoverage" />

    <!-- Execute code coverage for the DataTier assembly -->
    <property name="tests.dir"	value="DataTier.Tests\bin\Debug\" />
    <property name="assembly.to.test"	value="DataTier" />
    <property name="assembly.test.fixture"	value="DataTier.Tests.dll" />
    <property name="test.fixture.output.file"  value="${path::change-extension(assembly.test.fixture, '.xml')}" />
    <call target="AssemblyCoverage" />

    <echo message="Task execution finished at : ${script::format-to-string(datetime::now())}" />
  </target>

  <target name="AssemblyCoverage">
    <echo message="Task execution started at : ${script::format-to-string(datetime::now())}" />

    <ncover program="${ncover.console.file}"
            commandLineExe="${nunit.console.file}"
            commandLineArgs=""${assembly.test.fixture}" /xml:"${test.fixture.output.file}" /nologo"
            workingDirectory="${build.dir}\${tests.dir}"
            assemblyList="${assembly.to.test}"
            failonerror="true"
            logFile="${build.dir}\${tests.dir}\coverage.log"
            coverageFile="${build.dir}\${tests.dir}\coverage.xml"
            logLevel="Verbose" />

    <echo message="Task execution finished at : ${script::format-to-string(datetime::now())}" />
  </target>

In the script above, the three assemblies are code-coveraged in sequence by the target ExecuteTestCoverage. This sets up the name of the assembly to be tested and the name of the assembly containing the tests. It does this for each of the assemblies (BusinessTier.Tests, Common.Tests and DataTier.Tests). It then calls the worker target UnitTestAssembly which invokes NCover.Console and exercises the unit-tests within the assembly.

If you are adding code-coverage to your build script then strictly speaking you don't need to run the unit-tests separately, as your code-coverage will run the unit-tests anyway. However, I prefer to run them both separately as in the examples I have described here.

Summary

Hopefully I have given you sufficient information for you to be able to add unit-testing and / or code coverage to your own Nant build scripts. Feel free to leave a comment if you would like me to further elaborate on anything within this article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Gold-Vision CRM
United Kingdom United Kingdom
I am a professional software engineer and technical architect with over twenty years commercial development experience with a strong focus on the design and development of web and mobile applications.

I have experience of architecting scalable, distributed, high volume web applications that are accessible from multiple devices due to their responsive web design, including architecting enterprise service-oriented solutions. I have also developed enterprise mobile applications using Xamarin and Telerik Platform.

I have extensive experience using .NET, ASP.NET, Windows and Web Services, WCF, SQL Server, LINQ and other Microsoft technologies. I am also familiar with HTML, Bootstrap, Javascript (inc. JQuery and Node.js), CSS, XML, JSON, Apache Cordova, KendoUI and many other web and mobile related technologies.

I am enthusiastic about Continuous Integration, Continuous Delivery and Application Life-cycle Management having configured such environments using CruiseControl.NET, TeamCity and Team Foundation Services. I enjoy working in Agile and Test Driven Development (TDD) environments.

Outside of work I have two beautiful daughters. I am also an avid cyclist who enjoys reading, listening to music and travelling.

Comments and Discussions

 
GeneralMy vote of 5 Pin
reggaeguitar6-Jul-15 10:50
reggaeguitar6-Jul-15 10:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.