Noob to Pro Unit Testing

So we have done some serious work on our original code base. Before we do more we need to get over one of the hurdles going from Noob to Pro, lets look at adding unit testing to this projects. I realize that is weird coming from me since I cuss at unit testing but I still use them. I just complain a bunch. They are an important tool so long as you don’t abuse them.

Now, I use NUnit so all my syntax will be in that framework’s vocabulary. I am going to create a new project within the solution. I will go to NuGet and get the NUnit stuff and install on my testing project. Then I will add references from the original project to my new project and import them into the library. At some point I need to create a tutorial on that since I am sure that is as clear as mud. Alas that day is not today. If you want to check if I have gotten around to it then run a search and see if I have or comment below. That will encourage me to add that to the site.

We will have a class in our testing project called Number_Tests. This is where we will hold our Number test functions. Now, I have a testing template that I arrange every project in before I start unit tests. I setup, Setup and TearDown functions. Then I make my first test and use TestCase on it along with commenting in that function the arrange, act, assert pattern to help keep me on the straight and narrow.

My Unit Testing Skeleton

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        [SetUp]
        public void Setup()
        {
 
        }
 
        [TestCase()]
        public void test()
        {
            //arrange
            //act
            //assert
        }
 
 
 
        [TearDown]
        public void TearDown()
        {
 
        }
 
    }
}

From here we can start writing test.

Noob to Pro Unit Test 1, ParseSetValue()

Because we only have one function in our Number class, we know which test to start with in writing the unit tests. Normally, you write tests first then write code to force the test to true. We will do that for our next break down of the code. Here we are coming in maintenance coding style and retroactively adding tests.

using System;
 
namespace NoobToPro
{
    public class Number
    {
        public int value { getset; }
        public string sign { getset; }
 
        public void ParseSetValue(string valueToParse)
        {
            bool parseSuccess;
            int temp;
 
            parseSuccess = int.TryParse(valueToParse, out temp);
 
            if (parseSuccess)
            {
                value = temp;
            }
            else
            {
                throw new Exception("Value was not a number.");
            }
        }
    }
}

We know the function name so in the test file we will change test to that function, then add to it _test and finally the parameters. I generally find it easier to add an expected parameter rather than create a bunch of closely linked tests.It just feels too duplicatey to me. Don’t forget to add the functions parameters too.

Next we need to create a number. Rather than creating it in each class, we will create it globally and have the Setup and TearDown functions do their thing.

You should have something like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        Number number;
 
        [SetUp]
        public void Setup()
        {
            number = new Number();
        }
 
        [TestCase()]
        public void ParseSetValue_Test(string valueToParse, bool expectedValue)
        {
            //arrange
            //act
            //assert
        }
 
 
 
        [TearDown]
        public void TearDown()
        {
            number = null;
        }
 
    }
}

With that infrastructure out of the way, we can finally get to coding the test. First change the bool to int because that is what we are resolving. After that we will arrange, which is already done through setup, act,execute the function, and assert, check it against our expected value.

Finally in the TestCase start adding some parameters to test. The first value is a string to test then the second is a number to test.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        Number number;
 
        [SetUp]
        public void Setup()
        {
            number = new Number();
        }
 
        [TestCase("1", 1)]
        public void ParseSetValue_Test(string valueToParse, int expectedValue)
        {
            //arrange -done in setup
            //act
            number.ParseSetValue(valueToParse);
            //assert
            Assert.AreEqual(expectedValue, number.value);
        }
 
 
 
        [TearDown]
        public void TearDown()
        {
            number = null;
        }
 
    }
}

Go to the Test Explorer windows and Run All, you should pass.

That’s fine and dandy but what about nonsense?

Noob to Pro, Unit Test 2, It’s all nonsense

So we have a successful test. Go ahead and add some more. Just put another TestCase under the first and add them to your heart’s content. Don’t worry, I will wait.

With that done, lets move on to checking failing cases. Because a good fail is a good as a good success in unit testing. First we need to rename our test to reflect what it is testing. So from ParseSetValue_Test to ParseSetValue_Test_AreEqual.

Finally create your test as before only instead of Assert.AreEqual choose something else. Or don’t but you want to make sure some of your tests are of non numbers. When you run the tests it should fail because we have no logic for that.

If you do your tests well, you will see one mistake we made with making the number an integer. So all floating point numbers tests will fail.

Here I threw together a fairly crappy test. Which shows one of the missing things in the test. Our test does not handle non-numbers.

If you wanted to know one of the advantage of unit testing, Here it is. So let look at the test code for those having trouble following along.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        Number number;
 
        [SetUp]
        public void Setup()
        {
            number = new Number();
        }
 
        [TestCase("1", 1)]
        [TestCase("2", 2)]
        [TestCase("3", 3)]
        [TestCase("4", 4)]
        [TestCase("5", 5)]
        [TestCase("6", 6)]
        [TestCase("7", 7)]
        [TestCase("8", 8)]
        [TestCase("9", 9)]
        [TestCase("0", 0)]
        [TestCase(".", 0)]
        [TestCase("1.1", 1.1)]
        [TestCase("2.9", 2.9)]
        [TestCase("3.8", 3.8)]
        [TestCase("4.7", 4.7)]
        [TestCase("5.6", 5.6)]
        public void ParseSetValue_AreEqual(string valueToParse, int expectedValue)
        {
            //arrange -done in setup
            //act
            number.ParseSetValue(valueToParse);
            //assert
            Assert.AreEqual(expectedValue, number.value);
        }
 
        [TearDown]
        public void TearDown()
        {
            number = null;
        }
 
    }
}
Results of Test

Problem 1: Fixing Unsupported Floating Point Number

Because we have a test that fails, we should correct the code to make it pass. In Number.cs, lets change the int to float. If you try to run the test with just that you will get a failure. Think about why as I post the corrections to the Number class.

using System;
 
namespace NoobToPro
{
    public class Number
    {
        public float value { getset; }
        public string sign { getset; }
 
        public void ParseSetValue(string valueToParse)
        {
            bool parseSuccess;
            float temp;
 
            parseSuccess = float.TryParse(valueToParse, out temp);
 
            if (parseSuccess)
            {
                value = temp;
            }
            else
            {
                throw new Exception("Value was not a number.");
            }
        }
    }
}

You have not figured it out yet, here is the output.

Integer but we fixed the integers.

Ok, so I will quit toying with you and point out that you did not adjust your unit test for the new data type. Just so you won’t suffer any more, don’t forget your expected results typing. Make sure to type them correctly too.

Dealing with the period piece

We made some changes to the test lets check were we are at.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        Number number;
 
        [SetUp]
        public void Setup()
        {
            number = new Number();
        }
 
        [TestCase("1", 1f)]
        [TestCase("2", 2f)]
        [TestCase("3", 3f)]
        [TestCase("4", 4f)]
        [TestCase("5", 5f)]
        [TestCase("6", 6f)]
        [TestCase("7", 7f)]
        [TestCase("8", 8f)]
        [TestCase("9", 9f)]
        [TestCase("0", 0f)]
        [TestCase(".", 0f)]
        [TestCase("1.1", 1.1f)]
        [TestCase("2.9", 2.9f)]
        [TestCase("3.8", 3.8f)]
        [TestCase("4.7", 4.7f)]
        [TestCase("5.6", 5.6f)]
        public void ParseSetValue_Test_AreEqual(string valueToParse, float expectedValue)
        {
            //arrange -done in setup
            //act
            number.ParseSetValue(valueToParse);
            //assert
            Assert.AreEqual(expectedValue, number.value);
        }
 
        [TearDown]
        public void TearDown()
        {
            number = null;
        }
 
    }
}
15 down, 1 to go

So how do we handle bad data in the function, should someone sneak it in behind our backs? The young version of me would have limited the textbox to only numbers and periods. The slightly older would use regular expressions in the textbox object to solve the issue. The old me unit tests and you can’t ignore it. On the bright side the error is the one we threw so it is a handled error in that we want it to throw an error.

We have a choice. Fix the function to have it respond another way to the values or accept that it is correct and separate the unit test to make sure the thrown exception remains unmodified. It is your choice. Instead of throwing an exception you could have it return 0 as the value. Personally, I think it is behaving as intended and our unit testing is not testing the correct thing. We need a new test.

I feel like you could tell this when setting up the test case, I had it trying to return 0 but that is nonsensical but it had to return something right. Wrong, it was outside what that test could test. We want to throw an exception so having a test expecting a value is just weird.

So lets setup the exception test and wrap this long post up.

Noob to Pro Unit Testing Final Tests

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using NoobToPro;
 
namespace NoobToPro_Tests
{
    [TestFixture]
    public class Number_Tests
    {
        Number number;
 
        [SetUp]
        public void Setup()
        {
            number = new Number();
        }
 
        [TestCase("1", 1f)]
        [TestCase("2", 2f)]
        [TestCase("3", 3f)]
        [TestCase("4", 4f)]
        [TestCase("5", 5f)]
        [TestCase("6", 6f)]
        [TestCase("7", 7f)]
        [TestCase("8", 8f)]
        [TestCase("9", 9f)]
        [TestCase("0", 0f)]
        [TestCase("1.1", 1.1f)]
        [TestCase("2.9", 2.9f)]
        [TestCase("3.8", 3.8f)]
        [TestCase("4.7", 4.7f)]
        [TestCase("5.6", 5.6f)]
        public void ParseSetValue_Test_AreEqual(string valueToParse, float expectedValue)
        {
            //arrange -done in setup
            //act
            number.ParseSetValue(valueToParse);
            //assert
            Assert.AreEqual(expectedValue, number.value);
        }
 
        [TestCase(".")]
        [TestCase("$#.")]
        [TestCase("$#.432423")]
        [TestCase("+_/")]
        [TestCase("'~/?")]
        public void ParseSetValue_Test_ThrowsException(string valueToParse)
        {
            Assert.Throws<Exception>(() => number.ParseSetValue(valueToParse));
        }
 
        [TearDown]
        public void TearDown()
        {
            number = null;
        }
 
    }
}

So that is kind of my process of working through unit testing on existing code. Honestly it is harder than fresh writing it with unit tests but do a few programs with it. This reto adding unit testing will greatly improve your skill and help to take you from a noob to a pro.

Leave a Reply

Your email address will not be published.