04 April 2005

Unit Testing with NUnit

I downloaded NUnit version 2.2.0. To test that it was working properly I opened the bin\nunit.tests.dll and ran them. NUnit performed 605 tests which all passed so I guess it must work.

I started following Ron Jefferies Unit Testing examples. I got stuck because they didn't seem to work. It was telling me that .Assertion was deprecated and I should use .Assert instead. I didn't really want to get stuck on this so I decided to look for another "Getting Started" article.

I found an article on Unit Testing with C# on 4GuysFromRolla which has some sample code.
At first the TestClass wouldn't compile as the Person class was not visible. Anyway when I changed it to public I was able to compile it.

Then I tried modifying the Person class to be a Dice class with a single method Roll(), to roll the dice and return an integer, and a single property, Max to define the maximum value. I should have written the tests first.

To compile my dice class and it's test class I created a .bat file as follows:
csc /target:library dice.cs
csc /target:library /reference:nunit.framework.dll /reference:dice.dll TestDice.cs
The TestDice class references both the nunit testing framework and the dice.dll I want to create. Both .cs files are compiled as .dll's.

I wrote a test for the Max property and ran NUnit.

[Test]public void IsMax6()
{
Assert.AreEqual(6,dTest.Max);
}
It succeeded.

It was at this point that it dawned on me that choosing a dice as my first class to test was a bit daft. How do I test the roll method? The output is going to be random, I hope. I thought about testing the distribution of a number of rolls, but that would really be testing the random number generator from the .NET framework rather than testing my code. I decided I needed to test what I'm doing with the random numbers instead, whether I am scaling them correctly. Are the results in range?

I guess most of the time you are testing things that are repeatable or shouldn't be random. I decided to call the Roll() method a 100 times and check the maximum like this:-

[Test]public void isMaxRolled()
{
int j;
int maxRolled =
dTest.Min;
for(int i=0; i < j =" dTest.Roll();"> maxRolled)
{
maxRolled = j;
}
}
Assert.AreEqual(dTest.Max,maxRolled);
}

If I'm unlucky this could fail but most of the time it should pass. For some reason it was failing.

From having the Roll() method write a value to console and examining the Console output in NUnit I found out that I needed to create my Random object not in the Roll method but at the Dice object level, so that it is seeded once when the Dice is instantiated, not everytime Roll() is called. Otherwise with frequent calls you get the same seed value repeated for a while and then a new value.

I wrote a test for a Minimum property and found that I hadn't initialised it so the dice was actually rolling values from 0 to 6. After fixing that all the tests passed.

The code so far is…
TestDice.cs:

using System;
namespace NUnitTest
{
using NUnit.Framework;
///


/// TestDice for testing dice.dll
///

[TestFixture]
public class TestClass
{
Dice dTest;
public TestClass()
{
// TODO: Add constructor logic here
}


[SetUp]public void Init()
{
dTest = new Dice(1,6);
}


[Test]public void IsMax6()
{
Assert.AreEqual(6,dTest.Max);
}


[Test]public void IsMin1()
{
Assert.AreEqual(1,dTest.Min);
}


[Test]public void isMaxRolled()
{
int j;
int maxRolled = 0;
for(int i=0; i < j =" dTest.Roll();"> maxRolled)
{
maxRolled = j;
}
}
Assert.AreEqual(dTest.Max,maxRolled);
}


[Test]public void isMinRolled()
{
int j;
int minRolled =
dTest.Max;
for(int i=0; i < j =" dTest.Roll();" minrolled =" j;">


Dice.cs:
using System;
namespace NUnitTest
{
///
/// Dice - Random number generator.
///

public class Dice
{
int max;
int min;
Random
autoRand = new Random( );

[STAThread]
static void Main(string[] args)
{
Dice d = new
Dice(1,6);
}

Dice()
{
min = 1;
max = 6;
}

public Dice(int iMin, int iMax)
{
min = iMin;
max =
iMax;
}

public int Max
{
get{return max;}
set{max = value;}
}

public int Min
{
get{return min;}
set{min = value;}
}

public int Roll()
{
int i = Min + (int)((Max + 1 - Min) *
autoRand.NextDouble());
Console.WriteLine(i);
return i;
}
}
}

After this I went outside and planted three rows of potatoes because man cannot live by software alone...