Building and testing .NET application in command line

Setup: Windows – .NET 4.5, Linux – Mono 3, Mint 17 (based on Ubuntu 14). I need to build and test .NET application from command line and on CI server.
Sample solution can be downloaded from here https://github.com/mchudinov/BuildingTesting. Solution is compatible with MonoDevelop 5, Xamarin Studio 5 and Visual Studio 2012,2013,2015.

building_bricks

1. Building in command line

There are two standard command line building tools for .NET: MSBuild on Windows and xbuild on Linux/Mono. xbuild build files are compatible (with some exceptions) with MSBuild files.

We need at least 4 simple steps (targets as they called in MSBuild terminology) to build a .NET solution:

  • Clean
  • Restore NuGet packages
  • Build binaries
  • Run unit tests

Clean
Cleaning in a simplest case is just removing bin and obj directories.

MSBuild has RemoveDir task for this.

  <Target Name="Clean">
    <Message Text="Clean" />
    <RemoveDir Directories="BuildTest/bin; Test/bin;" ContinueOnError="False"/>
    <RemoveDir Directories="BuildTest/obj; Test/obj;" ContinueOnError="False"/>
  </Target>

Restore NuGet packages
NuGet packages must be always restored before building. Here is a good article about how restore NuGet packages on the right way. And here is the documentation form NuGet project about migrating MSBuild-Integrated solutions to use Automatic Package Restore.

My short conclusion:

  • folder packages must not be in source control
  • NuGet.exe and its config files must not be included in solution. It must be installed on build system

To restore packages nuget.exe must be installed on the build system and be in system Path. The latest version of the nuget.exe command-line tool is always available from http://nuget.org/nuget.exe
NuGet is a .NET program and is compatible with x86/x64 Linux system with Mono. Just download and save this file in /usr/bin folder and make it executable.

To restore packages just run simple command in command line from solution folder.

$ nuget.exe restore
Installing 'MSBuildTasks 1.4.0.78'.
Successfully installed 'MSBuildTasks 1.4.0.78'.
Installing 'NUnit 2.6.3'.
Successfully installed 'NUnit 2.6.3'.

In MSbuild file this can be executed by Exec task.

Restore target looks like this:

 <Target Name="Restore">
	<Message Text="Restore NuGet packages" />
    <Exec Command="nuget.exe restore" ContinueOnError="False"/>
  </Target>

Build binaries
MSBuild has a task named MSBuild to build a *.XYZproj files

  <Target Name="Build">
    <Message Text="Build Release" />
    <MSBuild Projects="BuildTest/BuildTest.csproj" Properties="Configuration=Release" ContinueOnError="False"/>
    <MSBuild Projects="Test/Test.csproj" Properties="Configuration=Release" ContinueOnError="False"/>
  </Target>

Run unit tests
Here we need an extension to standard MSBuild: MSBuild Community Tasks. Install it as a NuGet package to the UnitTest solution. Community Tasks extension has a special task called NUnit. A sample target can looks like this:

  <UsingTask AssemblyFile="packages/MSBuildTasks.1.4.0.78/tools/MSBuild.Community.Tasks.dll" TaskName="NUnit"/> 
  <Target Name="Test">
    <NUnit Assemblies="Test/bin/Release/Test.dll" ToolPath="/usr/bin" />
  </Target>

ToolPath attribute points to NUnit binary location.

We can make this build target operating system independent by adding operating system condition and pointing target to the right path where NUnit is installed on different OSes.

<UsingTask AssemblyFile="packages/MSBuildTasks.1.4.0.78/tools/MSBuild.Community.Tasks.dll" TaskName="NUnit"/> 
  <Target Name="Test">
    <NUnit Assemblies="Test/bin/Release/Test.dll" ToolPath="/usr/bin" Condition=" '$(OS)' == 'Unix'" />
	<NUnit Assemblies="Test/bin/Release/Test.dll" ToolPath="C:\Program Files (x86)\NUnit.org\nunit-console" Condition=" '$(OS)' == 'Windows_NT'" />
  </Target>

Note that NUnit version 3 the has only a binary named nuinit3-console.exe. To make MSBuild NUnit target work with NUnit v3 we need to copy nuinit3-console.exe to nuinit-console.exe.

Now we can create a Build.proj file, include all targets into it and store the file in the solution root folder. Not in any projects folders. It should be placed in the solution root. This file will be shown in Visual Studio or MonoDevelop under “Solution items”:

solutionitems_build

Simple Build.proj file:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Run">   

   <Target Name="Run">
    <CallTarget Targets="Clean" />
    <CallTarget Targets="Restore" />
    <CallTarget Targets="Build" />
    <CallTarget Targets="Test" />
  </Target>
 
  <Target Name="Clean">
    <RemoveDir Directories="BuildTest/bin; Test/bin;" ContinueOnError="False"/>
    <RemoveDir Directories="BuildTest/obj; Test/obj;" ContinueOnError="False"/>
  </Target>

 <Target Name="Restore">
    <Exec Command="nuget.exe restore" ContinueOnError="False"/>
  </Target>
  
  <Target Name="Build">
    <MSBuild Projects="BuildTest/BuildTest.csproj" Properties="Configuration=Release" ContinueOnError="False"/>
    <MSBuild Projects="Test/Test.csproj" Properties="Configuration=Release" ContinueOnError="False"/>
  </Target>

  <UsingTask AssemblyFile="packages/MSBuildTasks.1.4.0.78/tools/MSBuild.Community.Tasks.dll" TaskName="NUnit"/> 
  <Target Name="Test">
    <NUnit Assemblies="Test/bin/Release/Test.dll" ToolPath="/usr/bin" />
  </Target>
</Project>

Run build form command line with command xbuild Build.proj in solution folder. On WIndows platform it will be C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe Build.proj
There is a special predefined console window on Windows “Developer Command Prompt for VS” with MSBuild in PATH.

$ xbuild Build.proj
XBuild Engine Version 12.0
Mono, Version 3.2.8.0
Copyright (C) 2005-2013 Various Mono authors

Build started 12.10.2014 22:29:23.
__________________________________________________
Project "/home/mike/projects/BuildTest/Build.proj" (default target(s)):
	Target Run:
			Target Clean:
				Clean
			Target Restore:
				Restore NuGet packages
				Executing: NuGet.exe restore
				Все пакеты, перечисленные в packages.config, уже установлены.
			Target Build:
				Build Release
				Project "/home/mike/projects/BuildTest/BuildTest/BuildTest.csproj" (default target(s)):
					...
				Done building project "/home/mike/projects/BuildTest/BuildTest/BuildTest.csproj".
				Project "/home/mike/projects/BuildTest/Test/Test.csproj" (default target(s)):
					...
				Done building project "/home/mike/projects/BuildTest/Test/Test.csproj".
			Target Test:
				Run tests
				ProcessModel: Default    DomainUsage: Single
				Execution Runtime: mono-4.0
				.
				Tests run: 1, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0,023055 seconds
				  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
Done building project "/home/mike/projects/BuildTest/Build.proj".

Build succeeded.
	 0 Warning(s)
	 0 Error(s)

Time Elapsed 00:00:03.8330410

 

Note that on Windows MSBuild is not in PATH by default. Then to build application from command line a “Developer Command Prompt for VS” should be used.

C:\Program Files (x86)\Microsoft Visual Studio 11.0>msbuild d:\projects\C#\Mono\BuildingTesting\Buil
d.proj
Microsoft (R) Build Engine version 4.0.30319.18408
[Microsoft .NET Framework, version 4.0.30319.18444]
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 05.11.2014 10:53:25.
Project "d:\projects\C#\Mono\BuildingTesting\Build.proj" on node 1 (default targets).
Clean:
  Clean
  Removing directory "BuildTest/bin".
  rd /s /q "BuildTest/bin"
  Removing directory "Test/bin".
  rd /s /q "Test/bin"
  Removing directory "BuildTest/obj".
  rd /s /q "BuildTest/obj"
  Removing directory "Test/obj".
  rd /s /q "Test/obj"
Restore:
  Restore NuGet packages
  NuGet.exe restore
  All packages listed in packages.config are already installed.
Build:
  Build Release
....
Test:
  Run tests
  c:\lib\nunit\bin\nunit-console.exe /nologo Test/bin/Release/Test.dll
  ProcessModel: Default    DomainUsage: Single
  Execution Runtime: net-3.5
  .
  Tests run: 1, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0,106486926179191 seconds
    Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

Done Building Project "d:\projects\C#\Mono\BuildingTesting\Build.proj" (default targets).

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:03.13

C:\Program Files (x86)\Microsoft Visual Studio 11.0>

 

2. Testing in command line
To test .NET from command line NUnit must be installed as a command line program. On Windows download and install it from here http://nunit.org/index.php?p=download. On Debian-based Linux

$ sudo apt-get install nunit-console

Run unit tests in command line by pointing nunit-console program to test *.dll

$ nunit-console Test/bin/Release/Test.dll
NUnit-Console version 2.6.0.0
Copyright (C) 2002-2012 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment - 
   OS Version: Unix 3.13.0.24
  CLR Version: 4.0.30319.17020 ( Mono 4.0 ( 3.2.8 (Debian 3.2.8+dfsg-4ubuntu1) ) )

ProcessModel: Default    DomainUsage: Single
Execution Runtime: mono-4.0
.
Tests run: 1, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0,038198 seconds
  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

3. Build on TeamCity
Generally TeamCity can build both MSBuild and xbuild projects. There is a special build step runner called MSBuild for this purpose.
build1
But TeamCity version 8.1 (the last version at the time of writing this post is 8.1.5) does not (yet?) support xbuild Mono version 4.5. This issue is registered at TeamCity bug tracker: “Support Mono 4.5 profile“. Thus we can not build Mono 4.5 application with this build step. We can not build Mono 4.0 application with it either because xbuild is not shipped with Mono 4.0 and TeamCity can not find xbuild. Thus I build Mono application using command line builder.

 

build2

xbuild must be in path in this case.

3. Test on TeamCity

TeamCity has NUnit build step runner. Everything is quite straight forward here.
Use template **\*Test*.dll to test all test dlls. And **\obj\**\*Test.dll to exclude all obj-test dlls.

build3
If you get error something like
Could not load file or assembly 'CallQualityParser, Version=1.0.1.25142, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.

this means that test assembles and executable modules are build with different platforms. Just control that Test project and other project in solution have same platform setup. It should be x86 or ‘Any CPU’ or whatever but it should be the same across test and other projects.

Sample solution can be downloaded from here https://github.com/mchudinov/BuildingTesting. Solution is compatible with MonoDevelop version 5, Xamarin Studio version 5 and Visual Studio.