Monday, November 9, 2009

Unit Testing types for developers

Well you all know I'm a big fan of Unit Testing but did you know all the different types of tests you can run?  In this Blog I'll go over some of the examples:

A simple class to test

First we need a simple class to test so I'll use a very, very simple addition and division example called SimpleClass.  It contains two methods Add and Division both of which take two integers and return results.  Here is the basic code....

    public class SimpleClass
    {
        /// <summary>
        /// Add two values
        /// </summary>
        /// <param name="first">first value to add</param>
        /// <param name="second">second value to add</param>
        /// <returns>sum of both values</returns>
        public int Add(int first, int second)
        {
            return first + second;
        }

        /// <summary>
        /// Divide two values
        /// </summary>
        /// <param name="first">first value to divide</param>
        /// <param name="second">second value to divide</param>
        /// <returns>division of both values</returns>
        public decimal Divide(int first, int second)
        {
            return first / second;
        }
    }

Lets get down to the testing.

First off all you need a class decorated with the attribute [TestFixture] this will tell NUnit GUI to run the test from the external program.  Here you can see a basic test thats you've probably see a few times before.

    [TestFixture]
    public class TestingExamples
    {
        [Test(Description="Basic Test")]
        public void SimpleClass_Test_A_Simple_Method()
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }
    }

It's saying pass the values 10 and 20 into the Add method and expect to get the result 30.  Simple really, and probably the type of test you'l want to write over and over again.  Howere there are other options available.

Repeat Test

        [Test(Description = "Repeat Test"), Repeat(30)]
        public void SimpleClass_Repeat_Test()
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }

By adding the Repeat(30) attribute to the test as shown this will run the test 30 times.  In this example it's not really a very useful test but if for example you want to test load on a server or perhaps populate a number of rows in a database it can be very useful.

Multipule Value Test

        [Test(Description="Multivalue Test")]
        [TestCase(10,20,30)]
        [TestCase(1,2,3)]
        [TestCase(7,6,13)]
        public void SimpleClass_TestCase_With_Many_Inputs(int
firstInput, int secondInput, int expectedResult)
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(firstInput, secondInput);
            Assert.IsTrue(result == expectedResult, "Result was not correct!");
        }

Here I'm calling the test 3 times but passing 3 different values and testing against 3 different results.  By using the [TestCase] attribute you can just add more and more values to the same test.

Expected Exception Test

        [Test(Description = "Expected Exception Test")]
        [ExpectedException(typeof(DivideByZeroException),
ExpectedMessage = "divide", MatchType = MessageMatch.Contains)]
        public void SimpleClass_TestCase_Divide_By_Zero()
        {
            SimpleClass simpleClass = new SimpleClass();
            decimal result = simpleClass.Divide(0, 0);
        }

Here is a divide test that throws a divide by Zero error.  By using the ExpectedException attribute we catch the error thrown by the .Net function and then checks it's the correct one, in this case System.DivideByZeroException.  It also checks the message text for the text "divide" and uses the MessageMatch.Contains attribute to indicate the text can exist anywhere in the error message.

Explicit

        [Test(Description = "Only Run when Explicitly chosen"), Explicit]
        public void SimpleClass_Explicit_Test_Of_Method()
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }

This is an interesting one for us in that it will only run when you "explicitly" select it to run using TestRunner or the Nunit GUI, if not it will be ignored during test runs.  You'd use this function if you wanted to have a UI test or specific test that won't run on the Build server.

MaxTime and TimeOut

        [Test(Description = "Maxtime Test"), MaxTime(20)]
        public void  SimpleClass_Max_Time_In_Mili_Seconds()
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }

        [Test(Description = "Timeout Test"), Timeout(20)]
        public void SimpleClass_Ensure_Long_Running_Process_Does_Not_Time_Out()
        {
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }

Timed tests are great for performance tests, here we specify that both tests need to run within 20 miliseconds.  What's the difference between them?  Well not a lot really so it's probably best to stick to Timeout and ignore MaxTime.

Platform

        [Test(Description = ".Net exclude test"), Platform(Exclude = "NET-2.0")]
        public void SimpleClass_Exclude_DotNet_Tests()
        {
            // Other options relevant to us
            // WinXP, Win2003Server, Vista, Net-1.1, Mono
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(10, 20);
            Assert.IsTrue(result == 30, "Result was not correct!");
        }

Probably not something we'd use very often but it may be useful in the future.  Here we specify that we this test should not be run under .Net 2.  Again the test is not important here what is important is that we can say things like only run a test on a Windows 2003 Server or only run the test on XP.

Random Values

        [Test(Description = "Random value generation test")]
        public void SimpleClass_Random_Values(
            [Values(10, 20, 30)] int firstValue,
            [Random(1, 50, 5)] int secondValue)
        {
            // run 15 times.. 3 values by 5 random numbers
            SimpleClass simpleClass = new SimpleClass();
            int result = simpleClass.Add(firstValue, secondValue);
            int testResult = firstValue + secondValue;
            Assert.IsTrue(result == testResult, "Result was not correct!");
        }

Finally sometimes you just can't be assed coming up with test data so in this case we use the [Random] attribute.  By saying [Random(1,50,5) int ]  I'm saying choose a number between 1 and 50 and run that test 5 times.  I've also added 3 additional [Values] 10,20 and 30 this means the test will run 15 times, i.e. 3 by 5