Testing a Class Library

Once we have written the contract and base class for sorting algorithms, it is important to write tests before we start writing implementation classes. This way writing software is called Test Driven Development (TDD). Now, as a software engineer, I know that my code is just right (sarcasm intended), however, TDD enables me to write code while proving that my code is in fact correct.

TDD dictates to write tests first that fail and then write code that incrementally “fixes” the tests. However, I would provide another motivation to writing tests first – the ones that won’t fail until there is a faulty implementation. While working on a class library in a team setting, writing such tests ahead of time also allows other team members to contribute and their code can get coverage as they write.

As the architect of the library you also have piece of mind since you have already written assertions that would fail in case someone alters the behavior of the contract.

Discover Implementations of the Contract

The idea of writing tests that ensure contract compliance requires that we can discover implementations for a given contract. Below is a method that would go through my assembly and find all implementations of my base class:

private IList<ISortAlgorithm<T>> GetAllSortingAlgorithms<T>() where T : IComparable<T>
{
    Type[] typeArgs = { typeof(T) };
    Type baseType = typeof(SortBase<>);
    Assembly assembly = Assembly.GetAssembly(baseType);
    IList<ISortAlgorithm<T>> returnValue = new List<ISortAlgorithm<T>>();
    foreach (Type t in assembly.GetTypes())
    {
        if (t.IsAbstract)
        {
            continue;
        }

        if (t.IsInterface)
        {
            continue;
        }

        if (t.BaseType == null)
        {
            continue;
        }

        if (t.BaseType.Name == baseType.Name)
        {
            Type genericType = t.MakeGenericType(typeArgs);
            ISortAlgorithm<T> algorithm = Activator.CreateInstance(genericType) as ISortAlgorithm<T>;
            returnValue.Add(algorithm);
        }
    }

    return returnValue;
}

The code above expects to operate with generics that implement IComparable<T> interface. It constructs the generic type argument using Type.MakeGenericType and uses Activator.CreateInstance for creating a list of all implementations of SortingBase class.

It goes through the types defined inside the assembly and filters out all such types that are either abstract, interfaces or do not have a base class defined. Next, we inspect if the base type is SortBase and generate objects of all such types.

Tests

The tests for the contract are fairly straight forward and we execute them for each of the objects that we discovered above. These tests would pass even when there are no implementations and will automatically execute for any implementation that inherits from our base class (and ofcourse are part of the same assembly).

Below is the complete test class definition:

namespace Algorithms.Sorting.UnitTests
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;

    using Common;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class SortUnitTests
    {
        [TestMethod]
        public void SortHappyPathArray()
        {
            IList<ISortAlgorithm<string>> algorithms = this.GetAllSortingAlgorithms<string>();
            foreach (ISortAlgorithm<string> algorithm in algorithms)
            {
                string[] stringArray = new string[] { "usman", "Sharif", "Muhammad" };
                string failureMessage = $"Test failed for {algorithm.GetType().Name}";
                algorithm.Sort(stringArray);
                Assert.AreEqual("Muhammad", stringArray[0], failureMessage);
                Assert.AreEqual("Sharif", stringArray[1], failureMessage);
                Assert.AreEqual("usman", stringArray[2], failureMessage);
            }
        }

        [TestMethod]
        public void SortHappyPathArrayDescending()
        {
            IList<ISortAlgorithm<string>> algorithms = this.GetAllSortingAlgorithms<string>();
            foreach (ISortAlgorithm<string> algorithm in algorithms)
            {
                string[] stringArray = new string[] { "usman", "Sharif", "Muhammad" };
                string failureMessage = $"Test failed for {algorithm.GetType().Name}";
                algorithm.Sort(stringArray, SortOrder.Descending);
                Assert.AreEqual("usman", stringArray[0], failureMessage);
                Assert.AreEqual("Sharif", stringArray[1], failureMessage);
                Assert.AreEqual("Muhammad", stringArray[2], failureMessage);
            }   
        }

        [TestMethod]
        public void SortArrayNoElements()
        {
            IList<ISortAlgorithm<string>> algorithms = this.GetAllSortingAlgorithms<string>();
            foreach (ISortAlgorithm<string> algorithm in algorithms)
            {
                string[] stringArray = new string[0];

                // There should be no exception on this call.
                try
                {
                    algorithm.Sort(stringArray);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test failed for {algorithm.GetType().Name} with Exception {ex.ToString()}");
                }
            }
        }

        [TestMethod]
        public void SortArrayOneElement()
        {
            IList<ISortAlgorithm<string>> algorithms = this.GetAllSortingAlgorithms<string>();
            foreach (ISortAlgorithm<string> algorithm in algorithms)
            {
                string[] stringArray = new string[1] { "Usman" };
                
                // There should be no exception on this call.
                try
                {
                    algorithm.Sort(stringArray);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test failed for {algorithm.GetType().Name} with Exception {ex.ToString()}");
                }
            }
        }

        [TestMethod]
        public void SortHappyPathList()
        {
            IList<ISortAlgorithm<int>> algorithms = this.GetAllSortingAlgorithms<int>();
            foreach (ISortAlgorithm<int> algorithm in algorithms)
            {
                IList<int> intList = new List<int>() { 9, 4, 10, 2 };
                string failureMessage = $"Test failed for {algorithm.GetType().Name}";
                algorithm.Sort(intList);
                Assert.AreEqual(2, intList[0], failureMessage);
                Assert.AreEqual(4, intList[1], failureMessage);
                Assert.AreEqual(9, intList[2], failureMessage);
                Assert.AreEqual(10, intList[3], failureMessage);
            }
        }
        
        [TestMethod]
        public void SortHappyPathListDescending()
        {
            IList<ISortAlgorithm<int>> algorithms = this.GetAllSortingAlgorithms<int>();
            foreach (ISortAlgorithm<int> algorithm in algorithms)
            {
                IList<int> intList = new List<int> { 9, 4, 2, 10 };
                string failureMessage = $"Test failed for {algorithm.GetType().Name}";
                algorithm.Sort(intList, SortOrder.Descending);
                Assert.AreEqual(10, intList[0], failureMessage);
                Assert.AreEqual(9, intList[1], failureMessage);
                Assert.AreEqual(4, intList[2], failureMessage);
                Assert.AreEqual(2, intList[3], failureMessage);
            }
        }

        [TestMethod]
        public void SortListNoElements()
        {
            IList<ISortAlgorithm<int>> algorithms = this.GetAllSortingAlgorithms<int>();
            foreach (ISortAlgorithm<int> algorithm in algorithms)
            {
                IList<int> intList = new List<int>();

                // There should be no exception on this call.
                try
                {
                    algorithm.Sort(intList);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test failed for {algorithm.GetType().Name} with Exception {ex.ToString()}");
                }
            }
        }

        [TestMethod]
        public void SortListOneElement()
        {
            IList<ISortAlgorithm<int>> algorithms = this.GetAllSortingAlgorithms<int>();
            foreach (ISortAlgorithm<int> algorithm in algorithms)
            {
                IList<int> intList = new List<int> { 1 };
                
                // There should be no exception on this call.
                try
                {
                    algorithm.Sort(intList);
                }
                catch (Exception ex)
                {
                    Assert.Fail($"Test failed for {algorithm.GetType().Name} with Exception {ex.ToString()}");
                }
            }   
        }

        [TestMethod]
        public void SortExceptionWhenArgumentNull()
        {
            IList<ISortAlgorithm<string>> algorithms = this.GetAllSortingAlgorithms<string>();
            foreach (ISortAlgorithm<string> algorithm in algorithms){
                string[] stringArray = null;
                bool failed = false;
                try
                {
                    algorithm.Sort(stringArray);
                }
                catch (Exception ex)
                {
                    Assert.AreEqual(typeof(ArgumentNullException), ex.GetType());
                    failed = true;
                }

                if (!failed)
                {
                    Assert.Fail($"Test failed for {algorithm.GetType().Name}. Expected that ArgumentNullExcpetion will be thrown.");
                }
            }   
        }
        
        private IList<ISortAlgorithm<T>> GetAllSortingAlgorithms<T>() where T : IComparable<T>
        {
            Type[] typeArgs = { typeof(T) };
            Type baseType = typeof(SortBase<>);
            Assembly assembly = Assembly.GetAssembly(baseType);
            IList<ISortAlgorithm<T>> returnValue = new List<ISortAlgorithm<T>>();
            foreach (Type t in assembly.GetTypes())
            {
                if (t.IsAbstract)
                {
                    continue;
                }

                if (t.IsInterface)
                {
                    continue;
                }

                if (t.BaseType == null)
                {
                    continue;
                }

                if (t.BaseType.Name == baseType.Name)
                {
                    Type genericType = t.MakeGenericType(typeArgs);
                    ISortAlgorithm<T> algorithm = Activator.CreateInstance(genericType) as ISortAlgorithm<T>;
                    returnValue.Add(algorithm);
                }
            }

            return returnValue;
        }
    }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s