Saturday, December 7, 2013

Unit Testing, Part 4 (More Tips and Tricks)

Intro

In Part1, Part2, and Part3 we covered the mechanics of unit testing, why to use unit tests, we created some of our own unit tests, we saw a tip for making your unit tests smaller and more focused, and we gained some knowledge of Dependency Injection and how to use it to make your unit testing life easier. In this final piece on unit testing (admit it, you were hoping I'd lay off this topic), We'll cover just a couple more useful tricks with unit testing.

 Test Method and Test Class Initialization

We've already seen how unit test methods require an attribute on the method named TestMethod (example below).

[TestMethod]
public void TestStuff()
{
  //test some stuff here...
}

Well there are a couple other attributes that you can use on methods within your test classes that will save you some time, and they are ClassInitialize and TestInitialize. A method marked with the ClassInitialize attribute is automatically run (as part of running unit tests) only a single time regardless of the number of TestMethod methods. A method marked with the TestInitialize attribute is automatically run (as part of running unit tests) once for each test method within the test class. Let's look at an example:

    [TestClass]
    public class BaseTemplateValidatorTest
    {
        public static BaseTemplate m_object;
        IEntityValidator validator;

        [TestInitialize]
        public void TestInitialize()
        {
            m_object = new BaseTemplate();
            validator = ValidatorFactory.GetValidator(m_object);
        }

        /// 
        /// title trim
        /// 
        [TestMethod]
        public void TestTitleTrim()
        {
            m_object.Name = " b ";
            var temp = validator.IsValid;
            Assert.IsTrue(m_object.Name == "b");
        }

        /// 
        /// title too long
        /// 
        [TestMethod]
        public void TestTitleTooLongChop()
        {
            m_object.Name = "b".PadLeft(ValidationConstants.TemplateNameMaxLength + 1, 'a');
            var temp = validator.IsValid;
            Assert.IsTrue(m_object.Name.Length == ValidationConstants.TemplateNameMaxLength);
        }

        /// 
        /// title max length
        /// 
        [TestMethod]
        public void TestTitleMaxLength()
        {
            string originalName = "b".PadLeft(ValidationConstants.TemplateNameMaxLength, 'a');
            m_object.Name = originalName;
            var temp = validator.IsValid;
            Assert.IsTrue(m_object.Name.Equals(originalName));
        }
    }
Here we have a method in our test class decorated with the TestInitialize attribute. Within this method we initialize a couple of member objects that we use in multiple TestMethods. This means that we don't have to initialize these member objects within each test method, thus saving us some code.

A method marked with ClassInitialize must be static. Why? For every test method that is run during unit testing, a new instance of your test class is created. So, in order for a method related to that class to only be run a single time (which ClassInitialize decorated methods are), it must be static. Here's an example:
    [TestClass]
    public class UnitTest1
    {
        private static Class1 m_class1;

        [ClassInitialize]
        public static void InitalizeTheClassYo(TestContext testContext)
        {
            m_class1 = new Class1();
        }

        [TestMethod]
        public void AddStuffNullParams()
        {
            var actual = m_class1.AddStuff(null, null);
            Assert.AreEqual(-1, actual, "AddStuff should return -1 when both parameters are null");
        }

        [TestMethod]
        public void AddStuffNullParam1()
        {
            var actual = m_class1.AddStuff(null, 3);
            Assert.AreEqual(-1, actual, "AddStuff should return -1 when the first parameter is null");
        }

        [TestMethod]
        public void AddStuffNullParam2()
        {
            var actual = m_class1.AddStuff(4, null);
            Assert.AreEqual(-1, actual, "AddStuff should return -1 when the second parameter is null");
        }

        [TestMethod]
        public void AddStuffValidNumbers()
        {
            var actual = m_class1.AddStuff(4, 3);
            Assert.AreEqual(7, actual, "AddStuff should return the sum of 2 numbers when numbers are passed in");
        }
    }
The method InitializeTheClassYo is decorated with the ClassInitialize attribute, so it is run once and only once as part of running our unit tests. We use it here to initialize a static class member, thus saving ourselves a little code and a little bit of processing time.

ExpectedException Attribute

ExpectedException is another attribute you'll end up using to decorate your test methods. When you write a method that purposely throws an exception under certain conditions, the ExpectedException attribute is how you tell your unit test to expect the correct type of exception. Here's an example:
        public int AddStuff(int? param1, int? param2)
        {
            if (!param1.HasValue && !param2.HasValue)
                throw new ArgumentNullException("param1 and param2", "param1 and param2 cannot both be null");
            if (!param1.HasValue || !param2.HasValue)
                return -1;
            else
                return param1.Value + param2.Value;
        }
There's nothing special about the above method, in fact you might recognize this method from a prior post. You can tell what it does pretty easily by looking at it. Take special note of the fact that it throws an ArgumentNullException if both of the parameters are null. Now here is one of the unit tests for this method:
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void AddStuffNullParams()
        {
            var actual = m_class1.AddStuff(null, null);
        }
That lovely little ExpectedException attribute takes a parameter that is the type of exception to look out for, and as long as the code within this unit test throws an exception of the appropriate type, the test passes! As you may have guessed, if your unit test doesn't throw the exception (for example, if the code within was "var actual = m_class1.AddStuff(null, 34);", then the unit test would fail because the code doesn't throw the expected exception.

What's Next?

  • There are other unit-test-related atrtributes you can use on methods. Look them up, you might find them useful.
  • What other parameters can you pass in to the ExpectedException attribute?
  • How would you test a private or protected method/field? Why would you want to? What are some arguments against testing fields and methods scoped private or protected?

No comments:

Post a Comment