Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Adding unit testing and code coverage to your Nant scripts

5.00/5 (1 vote)
19 Sep 2014CPOL3 min read 12.1K  
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)