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