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
.
<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" />
<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" />
<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" />
<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
.
<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" />
<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" />
<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" />
<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.