Tuesday, February 25, 2014

Advanced Test-Driven Development

Introduction

At my current place of employment, I was recently introduced to the “Uncle Bob” series of videos, more properly titled Clean Coders. In episodes 19 through 23, Robert Martin begins a series on advanced test-driven development.

One of the things that was mentioned in that series is about how your tests should be treated no differently than production code. That means that your tests should also follow the SOLID principles: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation/Separation, and the Dependency Inversion principles (see the videos for clarifications surrounding applying these principles in relation to unit tests, however). Also, another good coding principle is DRY—don’t repeat yourself.

Traditional TDD

In .NET, I use the standard MS Test test framework. I know, it’s not great, and I should use NUnit or xUnit, right? But really, I don’t like NUnit (or xUnit). Actually, MS Test isn’t great either (too many attributes), but I like it better than NUnit or xUnit. Feel free to disagree with me. I'm not writing this blog post to argue about the best testing framework. See Roy Osherove's blog for a more in-depth discussion regarding the merits (or lack thereof) of various testing frameworks. (Hint: he has a very low opinion of MS Test—but that's OK, he's entitled to his opinion.)

Anyway, the point is, the MS Test framework does not support the notion of setup contexts (nor does NUnit or xUnit, for that matter). Yes, yes, there is a TestInitializeAttribute that you can adorn a method with to provide a test class with a setup method (and NUnit’s corresponding SetupAttribute attribute), but that’s not what I’m talking about.

Usually when we create tests, we create one test class for each class under test (CUT). Invariably, one of two things can take place when it comes to common data needed for tests (though, maybe not needed for each test):

  1. Create private test class fields and initialize them in the setup method adorned with the TestInitializeAttribute attribute
  2. Just re-create all test setup information within each test method in the Arrange section of the test


I usually follow number 2, but I was always bothered by it. After all, it violates DRY. I just hate it. But option 1 is no better! You end up instantiating a lot of fields and data for each and every test method in the class, regardless of whether the currently executing test needs all of the data. This can be terribly inefficient, especially if your tests involve creating expensive objects.

So how can we get small setup methods and small tests?

Advanced TDD with Context Classes

The solution to this problem is recognizing that there are various contexts under which your tests are running. Some contexts may be completely separate from others, while other contexts need to use data that also appears in a different context. The real trick is determining how to arrange the contexts so that you don’t violate the DRY principle and are able to promote re-use in order to keep setup methods small, all the while keeping your tests really small. I got the idea for this concept directly from the Clean Coders video series. (Please, if you hate this idea, don’t misconstrue the fact that because I got this idea from the Clean Coders video series that the series must be junk. It’s not. I just might have a bad idea here. I don’t think so; but hey, I’m entitled to my opinion. :) Of course, I’m always interested in feedback and open discussion.)

I first tried this concept out on some code at my place of employment. But of course, I can’t show you that code. So, the rest of this article is going to demonstrate this concept using a class called Category. While this class is really simple, and you may disagree with my implementation, this is not about the Category class implementation. It’s about how you can structure your tests to keep them SOLID, DRY, and focused.

It should be noted that the method outlined here may not work on Visual Studio 2010. It will work with Visual Studio 2012 and 2013, however. If anyone finds that the methods outlined here work on Visual Studio 2010, too, I’ll update this post accordingly.

Introducing the Category Class

In order to demonstrate the concept, I’m going to build up the Category class incrementally, practicing TDD as I go along. So, without further delay, let’s get started.

First, I want to outline some invariants of the Category class:

  • A category name cannot be null or the empty string and it cannot be a whitespace only string. If any of these conditions are true, throw an InvalidCategoryNameException exception.
  • A category can have a single parent. The parent cannot be null—the parent property must always return an object.
  • A category can have sub-categories. You cannot add a null sub-category. Adding a sub-category must not result in a cycle, e.g. A -> B -> C -> A, where the first and last category A are the same exact category object.


As I go along, I’ll introduce other business rules and/or requirements. So, what test should I write first? Well, I think I'll write a test to make sure that I can create a Category.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DDDSportsStore.Domain.Tests
{
    [TestClass]
    public class CategoryTests
    {
        [TestMethod]
        public void Can_create_a_category()
        {
            Category c = new Category();
        }
    }
}

Now, of course this test won’t compile because I haven't yet written the Category class. So I'm going to go ahead and create that now.

using System;
using System.Collections.Generic;
using System.Linq;

namespace DDDSportsStore.Domain
{
    public class Category
    {
    
    }
}

There, that's all that's needed in order to make the test above pass. If you haven’t guessed, this category class is implementing a product category that you might find as part of an online e-commerce catalog. I’m trying to practice domain-driven design (DDD) with an example used in the Pro ASP.NET MVC4 book published by APress. Enough about DDD; that’s another story and another blog post (or 10).

Now that I have a passing test and I can indeed create a category object, I'm going to get rid of that first test and replace it with a test that ensures that I cannot create a category with a null name. It would be really hard in a blog format to show the incremental building up of the category class in order to satisfy the test (see episode 24 of Clean Coders on the Transformation Priority Premise), so I'm just going to show you the final implementation of this particular constructor that will pass the test.

[TestMethod]
[ExpectedException(typeof(InvalidCategoryNameException))]
public void Cannot_create_a_Category_with_a_null_string_for_a_name()
{
    Category c = new Category(null);
}

Pay no attention to the ExpectedExceptionAttribute attribute. I'll be replacing that soon enough. Just know that that attribute is a real code smell and could be causing your tests to pass when they shouldn't be. OK, with that said, of course this test should also fail because I have no constructor that takes a parameter. I'm going to implement that constructor now.

public class Category
{
    private string name;

    public Category(string name)
    {
        if (name == null)
            throw new InvalidCategoryNameException();

        Name = name;
    }

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

Ok, the next part of the first invariant is that the string cannot be empty. I'll write another test to test this invariant.

[TestMethod]
[ExpectedException(typeof(InvalidCategoryNameException))]
public void Cannot_create_a_Category_with_an_empty_string_for_a_name()
{
    Category c = new Category(string.Empty);
}

Again of course, this test fails because in the constructor, I only checked to see if name is null. So now I'll update the constructor to make this test pass.

public Category(string name)
{
    if (string.IsNullOrEmpty(name))
        throw new InvalidCategoryNameException();

    Name = name;
}

There, that should be enough to make the above test pass. The final invariant regarding a category's name is that it cannot consist entirely of white space. The test for this invariant will closely resemble the other two tests. So I think it's time to refactor these tests to remove code duplication and use composable assertions (again, see the Clean Coders videos). The first thing you'll notice about the tests is that I attempt to assign the newly created category to a variable named c. This isn't strictly necessary—I merely need to call the constructor with an invalid category name and the execption should be thrown. So I'll eliminate that duplication in this refactoring. I'll also create a helper method to carry out the assertion. Finally, I'm testing 3 components of a single logical invariant concerning the name of a category. So I could really check all 3 components in a single test and I would not be violating the "single assertion per test" rule. The new tests are shown below.

[TestClass]
public class CategoryTests
{
    [TestMethod]
    public void Cannot_create_a_Category_with_an_invalid_name()
    {
        AssertCannotCreateCategoryWithName(null);
        AssertCannotCreateCategoryWithName(string.Empty);
        AssertCannotCreateCategoryWithName("\n");
    }

    public void AssertCannotCreateCategoryWithName(string name)
    {
        ExceptionAssert.Throws<InvalidCategoryNameException>(
            () => new Category(name)
        );
    }
}

public static class ExceptionAssert
{
    public static void Throws<T>(Action action) where T : Exception
    {
        try
        {
            action();
            Assert.Fail(
                "Expected '{0}' to be thrown but no exception was thrown.",
                typeof(T).FullName
            );
        }
        catch (T)
        {
            return;
        }
        catch (AssertFailedException)
        {
            throw;
        }
        catch (Exception e)
        {
            Assert.Fail("Expected '{0}' to be thrown, but '{1}' was thrown instead.", 
                        typeof(T).FullName,
                        e.GetType().FullName
            );
        }
    }
}

Remember I told you that I'd be taking care of that ExpectedExceptionAttribute attribute. This is one instance where NUnit and xUnit really has something on MS Test (syntactically), namely the Assert.Throws<T>(...) method. I hate the ExpectedExceptionAttribute now because it messes with the Arrange, Act, and Assert pattern of TDD. Plus, it can mask other problems with your code. So instead, I get rid of the attribute and use a custom static class modeled after the Assert and CollectionAssert classes in the standard MS Test test framework. So, in my test above, I added a call to my new assertion method trying to create a category with a whitespace only name. The constructor currently only looks for null and the empty string, so this test will fail. It's a simple matter of updating the guard clause in the constructor to make this test pass:

public Category(string name)
{
    if (string.IsNullOrWhiteSpace(name))
        throw new InvalidCategoryNameException();

    Name = name;
}

That's it. The test above should now pass and I've ensured that the Category class is enforcing all of the invariants on its name. There are a few more tests I need to write before I get to the concept of creating context classes. I'm going to skip the step-by-step, but I will show you the final tests that I created to enforce some of the category's Subcategory property invariants (the ones that I don't enforce will be enforced later, as I continue to develop this class). I additionally included a test that ensures that any whitespace that appears in a category name is replaced with a single space character. I'll also show you the Category class as it stands after having written the tests.

[TestClass]
public class CategoryTests
{
    private string PARENT;
    private Category parent;

    [TestInitialize]
    public void CategoryTestsSetup()
    {
        // It's important to initialize test state here, because if a test 
        // (in-)advertently changes the variable's state, it could affect 
        // other tests.
        PARENT = "Parent";
        parent = new Category(PARENT);
    }

    // ...

    // Our other tests we wrote previously are up above.

    [TestMethod]
    public void
    A_category_name_is_sanitized_to_contain_only_1_space_character_between_words()
    {
        AssertCategoryNameWhitespaceIsCollapsed(
            "A  category  with  extraneous  whitespace", 
            "A category with extraneous whitespace"
        );
        AssertCategoryNameWhitespaceIsCollapsed(
            "A\r\ncategory\twith\vother\fwhitespace characters", 
            "A category with other whitespace characters"
        );
        AssertCategoryNameWhitespaceIsCollapsed(
            "This category name is ok", 
            "This category name is ok"
        );
    }

    private void
    AssertCategoryNameWhitespaceIsCollapsed(string name, string expectedName)
    {
        Assert.AreEqual(new Category(name).Name, expectedName);
    }

    [TestMethod]
    public void A_top_level_category_returns_its_name_when_ToString_is_called()
    {
        Assert.AreEqual(PARENT, parent.ToString());
    }
}

Now for the Category class as it stands now.

public class Category
{
    public static readonly Category None = new NullCategory();
    private string name;
    private List<Category> subCategories;

    protected Category()
    {
        subCategories = new List<Category>();
    }

    public Category(string name) : this()
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new InvalidCategoryNameException();

        this.name = SanitizeName(name);
    }

    public virtual string Name
    {
        get { return name; }
        set { name = SanitizeName(value); }
    }

    public IReadOnlyCollection<Category> Subcategories 
    {
        get { return subCategories.AsReadOnly(); }
    }

    public virtual void AddSubcategory(Category c) 
    {
        if (this == Category.None)
            throw new InvalidOperationException(
                "Unable to add a subcategory to the 'None' category."
            );

        if (c == null || c == Category.None)
            throw new InvalidSubcategoryException();

        if (subCategories.Any(sc => sc.Name == c.Name))
            throw new DuplicateSubcategoryException();

        subCategories.Add(c);
    }

    private string SanitizeName(string proposedName)
    {
        return Regex.Replace(proposedName, @"\s+", " ");
    }

    #region System.Object overrides

    public override string ToString()
    {
        return Name;
    }

    #endregion

    /// <summary>
    /// This class represents an implementation of the Null Object pattern 
    /// (a/k/a Special Case pattern) and is meant to be used anywhere
    /// where you might otherwise return null.
    /// </summary>
    private sealed class NullCategory : Category
    {
        public override Name
        {
            get { return string.Empty; }
            set { }
        }
    }
}

So ordinarily, I would have written two unit tests for each of the invalid subcategory conditions asserted in the tests above; but instead, I chose to use a custom assertion method and perform one logical assert. You can also see that I have two new private member variables in the test class which are initialized before each test. But only two of the three tests need these private members. Now, ordinarily, this isn't all that bad and I wouldn't bother with factoring out a context class. But what if the tests were much more involved in the setups that are needed? For instance, at my place of employment, I recently had 7 tests, 2 of which required the same 20 or 30 lines of setup code, the other 5 of which required at least another 10 common lines of setup code between them. All of this code would have been repeated in the methods requiring the code. Or worse, all 50 or so lines would have been in a single setup method, with all of that setup occurring before each test, even if a particular test didn't require all that was setup.

You can also see more fleshed out versions of the Category class. I also created a nested, private category class that is an implementation of the Null Object (a/k/a Special Case) Pattern to represent the null category. Implementing this pattern allows programmers to not have to check for null all the time before accessing properties and fields of objects. Because it's a nested, private class, it cannot be instantiated by anything except the Category class. This allows the NullCategory to be exposed as a public static field on the Category class as a Singleton.

Now, I have not addressed setting the parent category of a category. By default, a category's parent is set to the Singleton NullCategory. But before I tackle that, I want to override the System.Object.Equals method. And when I override this method, I also need to override the System.Object.GetHashCode method and the equality and inequality operators.

The Equality Context Test Class

My motivation for overriding the Equals method and the (in)equality operators is for easy comparison of two categories in order to determine whether or not they are the same. Also, the implementation I have in mind will prevent two subcategories with the same name being added to the same parent. I think you'll see how I accomplish this very easily without much further explanation, so let's hop to it.

Of course, before I can write any code to implement equality testing, I'll need to write the tests. In order to test equality, I'll need two categories. But I'll only need them for this set of tests. I don't want to drag these categories around for each and every other test I might have surrounding categories. So, I think it's best at this point to create a context class for testing category equality. (Again, this example is very simplistic and contrived, and probably not worthy of this level of breakdown; but keep in mind the real-life scenario I mentioned above with 50 lines of code needed by some of the various tests throughout a single CUT. The benefits are potentially huge: smaller test setups; faster tests; smaller, easier to read, and maintainable tests.)

Ok, so below are the tests for the Category class I've been working with (somewhat abbreviated), showing the new context class. I'll also show you my implementation of the equality method and operators in the abstract category class.

[TestClass]
public abstract class CategoryTests
{
    private string PARENT;
    private Category parent;

    [TestInitialize]
    public void CategoryTestsSetup()
    {
        // It's important to initialize test state here, because if a test 
        // (in-)advertently changes the variable's state, it could affect 
        // other tests.
        PARENT = "Parent";
        parent = new Category(PARENT);
    }

    // ...

    // Our other tests we wrote previously are up above.
    [TestClass]
    public class CategoryEqualityContext : CategoryTests
    {
        Category categoryA;
        Category categoryB;

        [TestInitialize]
        public void CategoryEqualityContextSetup()
        {
            categoryA = new Category("A");
            categoryB = new Category("B");
        }

        // This method was moved from the outer context because ToString is 
        // used in determining equality, so it makes sense to move it to this
        // context.
        [TestMethod]
        public void A_top_level_category_returns_its_name_when_ToString_is_called()
        {
            Assert.AreEqual("A", categoryA.ToString());
        }

        [TestMethod]
        public void
        Can_determine_whether_or_not_two_categories_are_equal_to_one_another()
        {
            Assert.AreEqual(categoryA, categoryA);
            Assert.IsTrue(categoryA.Equals(categoryA));
            Assert.IsTrue(((object)catgeoryA).Equals((object)categoryA));
            Assert.IsTrue(categoryA.Equals(new Category("A")));
        }

        [TestMethod]
        public void
        Can_use_the_equality_operator_to_determine_whether_two_categories_are_equal_to_one_another()
        {
            Category c = new Category("A");
            Assert.IsTrue(c == categoryA);
        }

        [TestMethod]
        public void
        Can_use_the_inequality_operator_to_determine_whether_two_categories_are_not_equal_to_one_another()
        {
            Assert.IsTrue(categoryA != categoryB);
        }
    }
}

So let's go over these tests before we get to the implementation in Category. The way MS Test works with nested classes like this is that the outer class is instantiated first, then the inner class, just like any other nested class structure in C#. But in addition to that, the outer class's method marked with the TestInitializeAttribute attribute is also run prior to the nested class's method marked with the same attribute. If the nested class doesn't define such a method, the outer class's method still runs before each and every test in the inner class. So what does that mean for these tests? Well, in this instance, it means that the PARENT string and parent category are carried along into the CategoryEqualityContext. While in this trivial example this isn't a big deal, this is not ideal in a situation where you have a lot of setups occurring in the outer class which the inner class methods don't need. Actually, we're going to refactor our tests to remove the outer class setups in just a bit. But I wanted to show you this so I could explain how the nested context classes work.

Secondly, notice that I've made the CategoryTests class abstract. This is very important. The CategoryEqualityContext class inherits from the CategoryTests class. This also means that it inherits all the methods of its base class. So, let's assume that the CategoryTests class isn't abstract and follow the flow of execution. The CategoryTests class is marked with the TestClassAttribute attribute, so MS Test looks for all the test methods in that class and executes them. Then MS Test sees a public, nested class called CategoryEqualityContext and finds all of its test methods and executes them. But because the CategoryEqualityContext class inherits from CategoryTests class, the tests in CatgoryTests run again as part of CategoryEqualityContext. Now you can see why it's very important to make the CategoryTests class abstract. The general rule of thumb, then, is the following:

If you have nested context test classes, make all test classes abstract except the inner-most context class; leave that class concrete. This ensures that your tests execute only once.
There is one more caveat to using nested context classes for scoping your test setups; I'll get to that later in this blog post.

OK, now that you know how nested context classes work, I'm going to show you how I implemented equality checking in the Category class to make the above tests pass.

public class Category
{
    // ...
    // All other methods previously shown are ansumed to exist above.

    public bool Equals(Category other)
    {
        if ((object)other == null)
            return false;

        return this.ToString() == other.ToString();
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
            return false;

        Category c = obj as Category;
        if ((object)c == nul)
            return false;

        return this.ToString() == c.ToString();
    }

    public override int GetHashCode()
    {
        return this.ToString().GetHashCode();
    }

    public static bool operator ==(Category a, Category b)
    {
        if (object.ReferenceEquals(a, b))
            return true;

        if ((object)a == null || (object)b == null)
            return false;

        return a.ToString() == b.ToString();
    }

    public static bool operator !=(Categoriy a, Category b)
    {
        return !(a == b);
    }
}

So there's not too much to be said about my implementation above. It pretty much follows the standard suggested implementation. Equality merely consists of checking whether or not 1) the acutal objects are the same object reference, or 2) the ToString method returns the same string. The only thing to note is that I have no concept of a parent category yet. When I finally add the ability to set a parent category, this will change my implementation of ToString.

Even More Nested Context Classes

So now you have an idea of how to use nested context classes in order to organize your test setups. While the simple example above gave you an at a glance view, let's make this a bit more concrete by implementing the rest of the functionality. To do that, let me tell you some other rules surrounding categories. First, you cannot have two subcategories of a single category that have the same name. You cannot add the None category as a subcategory (we already covered that). A category's parent cannot be null, but it can be None, meaning it's a "top-level" category. In order to make checking for duplicate subcategories really quick and easy, we're going to change the implementation of ToString. The implementation I have in mind will afford a couple of benefits:

  • Allows very quick determination of whether or not a category can be added as a subcategory
  • Allows very quick determination of whether or not two categories are the same by checking their path
  • Disallows cycles, e.g. A -> B -> C -> A does not form a cycle.


A note about the cycle point above: we'll have a method that ensures that the new parent isn't already a descendant of the current category. The method that's used is recursive. Technically, if you have extremely many levels of nested categories (e.g. A -> B -> C -> D -> ... etc.), it's possible to blow out the stack. But generally speaking, a product catalog for instance, will usually only have 2 - 5 levels of nesting; and that's being generous on the high-end. I guess a way to avoid a potential StackOverflowException would be to limit the allowable amount of nesting. But given the general use-case for this object—and mostly because this is just an example for this blog article and not something I'd use in production code—I'm not inclined to do so. I'll leave it to you as an exercise. Also, my current implementation of AddSubcategory will need to change. We'll also add a new constructor to the class.

Again, even though I told you what I'm going to implement in the class, all of the implementation was based on the tests that I wrote. So here are the new (and final) set of tests.

[TestClass]
public class CategoryTests
{
    // ...
    // All the other tests in the outer context are above.
    // The original setup method was moved to another nested 
    // context to be shown below, along with the test that
    // required the setups.

    [TestClass]
    public abstract class CategoryEqualityContext : CategoryTests
    {
        // ...
        // Nothing has changed in this test class from above.
    }

    [TestClass]
    public abstract class ParentCategoryContext : CategoryTests
    {
        // These variables and the setup method were moved from
        // the outer CategoryTests context class
        private string PARENT;
        private Category parent;

        [TestInitialize]
        public void ParentCategoryContextSetup()
        {
            PARENT = "Parent";
            parent = new Category(PARENT);
        }

        [TestMethod]
        public void Can_add_a_subcategory_to_a_category()
        {
            parent.AddSubcategory("subcategory");
            Assert.AreEqual(1, parent.Subcategories.Count);
        }

        [TestMethod]
        public void Cannot_add_a_duplicate_subcategory_to_a_category()
        {
            parent.AddSubcategory("subcategory");
            ExceptionAssert.Throws<DuplicateSubcategoryException>(
                () => parent.AddSubcategory("subcategory")
            );
        }

        [TestMethod]
        public void A_top_level_category_returns_its_name_when_ToString_is_called()
        {
            Assert.AreEqual(PARENT, parent.ToString());
        }

        [TestClass]
        public class ChildCategoryContext : ParentCategoryContext
        {
            private string CHILD;
            privte Category child;

            [TestInitialize]
            public void ChildCategoryContextSetup()
            {
                CHILD = "Child";
                child = parent.AddSubcategory(CHILD);
            }
    
            [TestMethod]
            public void
            A_subcategory_returns_its_parents_name_with_a_path_separator_and_its_own_name_when_ToString_is_called()
            {
                Assert.IsTrue(string.Concat(PARENT, "/", CHILD) == child.ToString(), 
                    string.Format("Expected '{0}/{1}' but got '{2}'", PARENT, CHILD, child.ToString()
                );
            }

            [TestMethod]
            public void
            Cannot_set_category_parent_if_new_parent_is_a_descendant_of_the_category()
            {
                Category grandchild = child.AddSubcategory("Grandchild");

                // Attempt parent/child/grandchild/parent where 'parent' 
                // at both the head and tail of the list are the same object.
                ExceptionAssert.Throws<invalidoperationexception>(() => parent.Parent = grandchild);
            }
        }
    }
}

After implementing the code that ought to make these tests pass, one of the tests above will actually fail. Can you spot which test will fail? Don't try too long to find it. It's really subtle, and is one of a small list of cons when using this style of testing. The test that will fail is A_top_level_category_returns_its_name_when_ToString_is_called(). But why should that fail? It looks just fine. Again, it's really subtle. The good news is that the test fails and doesn't produce a false positive (e.g. a passing test that should fail). Remember the rule I stated above concerning derived test classes. The most derived test class should be concrete while all lesser derived test classes and the base class must be abstract. In the concrete test class in the ChildCategoryContextSetup() method, I call parent.AddSubcategory(CHILD);. So a new subcategory is added to the parent category. So the parent category now contains 1 subcategory (because the setup runs prior to any tests executing). So now when I execute the A_top_level_category_return_its_name_when_ToString_is_called(), an additional subcategory is added to the parent category (for a total of 2 subcategories) and the assertion fails. There are two ways to fix this:

  1. Move the test to the ChildCategoryContext class. I don't like this option because to me, it seems like since it's only using the parent category, it should reside in the ParentCategoryContext class.
  2. Obtain the count of subcategories of the parent category prior to adding a subcategory. Then assert that the count of subcategories is equal to one plus the count obtained before acting on the CUT. To me, this is a much better option. The test is still straight-forward; the class resides where you'd expect it to reside; the test is less fragile.


I think I'll go with option number two and re-write the test as follows:

[TestMethod]
public void A_top_level_category_returns_its_name_when_ToString_is_called()
{
    int numSubcategories = parent.subCategories.Count;
    parent.AddSubcategory("subcategory");
    Assert.AreEqual(numSubcategories + 1, parent.Subcategories.Count);
}

With that done, that's it for the tests. Now for the implementation in the Category class.

public class Category
{
    // ...
    // All the old fields are above

    private Category parentCategory;

    // The constructors are have changed a bit.

    protected Category()
    {
        subCategories = new List<Category>();
    }

    public Category(string name) : this(name, None) { }

    // This constructor is private so that we can enforce not allowing
    // cycles to occur in parent/child/grandchild relationships.
    private Category(string name, Category parent) : this()
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new InvalidCategoryNameException();
        
        // You shouldn't call virtual properties and methods from your constructors,
        // so use the fields directly.
        this.name = SanitizeName(name);

        if (parentCategory == null)
            parentCategory = parent;
        else // Use SetParentCategory(...) to prevent cyclic relationships.
            SetParentCategory(parent);
    }

    // ...
    // The other properties listed before remain the same.

    public virtual Category Parent
    {
        get { return parentCategory; }
        set 
        {
            // Since on creation parentCategory is never left null, 
            // always call this method when setting the parent.
            SetParentCategory(value);
        }
    }

    // The original implementation of AddSubcategory was made private so
    // that external users can no longer pass in an arbitrary category.
    // This prevents cycles in the hierarchy.
    // 
    // The new public method is now a sort of factory method that creates
    // a new category, adding it as a subcategory to the current instance,
    // and returning the newly creatd category.

    private void AddSubcategory(Category c)
    {
        // ...
        // The original method stays the same...it's just private now.
    }

    /// <summary>
    /// Creates a new Category as a subcategory of the current instance.
    /// </summary>
    /// <param name="name">The name of the new subcategory.</param>
    /// <exception cref="InvalidOperationException">
    ///     Thrown if the current instance is <see cref="Category.None"/>.
    /// </exception>
    /// <exception cref="InvalidCategoryNameException">
    ///     Thrown if the <paramref name="name"/> is <c>null</c>,
    ///     the empty string, or consists entirely of whitespace.
    /// </exception>
    public virtual Category AddSubcategory(string name)
    {
        if (this == None)
            throw new InvalidOperationException();
        
        Category subcategory = new Category(name, this);
        AddSubcategory(subcategory);
        return subcategory;
    }

    private void SetParentCategory(Category c)
    {
        if (c != Category.None && this.HasDescendant(c))
            throw new InvalidOperationException();

        parentCategory = c;
        if (parentCategory == Category.None)
            parentCategory.AddSubcategory(this);
    }

    // This method determines whether or not the new parent passed to
    // SetParentCategory is a descendant of the category on which
    // SetParentCategory was called. If so, this method returns true.
    // This prevents cycles in the hierarchy.
    // Again, limiting hierarchy depth can prevent possible stack overflows
    // due to the recursive nature of this function.
    private bool HasDescendant(Category c)
    {
        bool result = false;

        if (this == c)
            result = true;
        else if (c.Parent != Category.None)
            result = this.HasDescendant(c.Parent);

        return result;
    }

    // The implementation of this method changed. Instead of simply returning
    // the name of the category, it returns the category's path from the root:
    // e.g. A/B/C, or Parent/Child/Grandchild.
    public override string ToString()
    {
        return Parent == Category.None ?
            Name : string.Concat(Parent.ToString(), "/", Name);
    }
}

There you go, that wraps up all of the code for the Category class. Let's just take a few seconds to review it. We finally added a Parent property. In addition, we added a private constructor that creates a new category and sets its parent. This constructor is private so that I have control over preventing cycles in the hierarchy. The original constructor that takes a category name defers to this new constructor, setting the parent category to Category.None. We made the original implementation of AddSubcategory private. The original implementation allowed you to pass in any Category instance, which could allow cycles to occur. So a new method was added that acts as a Factory Method, adding the newly created category as a subcategory of the instance on which it was called and returning the newly created subcategory. The SetParentCategory method is also interesting. It checks that if the category passed into it is not Category.None, then that the category is also not a descendant of the current instance—again, preventing cycles in the hierarchy. Finally, the ToString implementation changed to return the full path of the category from the root of its hierarchy, e.g. Parent/Child/Grandchild.

Conclusion

Again, the Category class is fairly small. Having said that, this ended up being a very long blog article. But I think that because of its relative simplicity, it made a good example of how using nested test classes to provide context boundaries for test setup methods can help you keep your setup methods small. Using context classes and setup methods, and keeping your setup methods small promotes the following benefits:

  • Small setups: Smaller setups mean that less data is being instantiated and configured; and only the data required for the tests in the current context is setup. This has additional benefits:
    • Faster execution times (OK, this could be anywhere from negligible to very noticeable)
    • Less chance for contamination to occur between tests, e.g. mutated state affecting downstream tests (though with the caveat seen earlier in this post)


  • Using setup methods allows you to keep your tests DRY
  • Using context classes in addition to setup methods keeps your tests small and easy to read
  • Small tests are focused, and usually adhere to the SRP. This means your tests are:
    • easy to maintain
    • flexible (read, not fragile)


  • By going one step further and using composable assertions, you make your tests even more understandable, easier to maintain, and less fragile.

There is a corresponding list of cons when it comes to using this style of testing, so to be fair, I'll list out the ones that I can immediately see.

  • Deep nested classes and the indenting could be annoying
  • Tests might be hard to find (heck, there's always CTRL+F)
  • If you're not careful with your assumptions, derived class setups can interfere with base class tests
    • This is why it's even more important to not just start writing your tests in this style, but after you've written a few and refactored a bit, you can be more confident about the arrangement of your contexts.
  • You have to remember to mark all base-class tests abstract and leave the inner-most class concrete.
  • Not all test runners support abstract base classes (AutoTest.Net for one)


I'm sure you might be able to think of some other pros or cons. Hopefully, though, you'll agree that the benefits listed above may be worth the little bit of extra effort. Before leaving you, I want to touch a little more on the effort that went into making these tests hierarchical.

The effort to make these hierarchical test classes was very small. Granted, this example class is not that complex and not that large. At my current place of employment, I had already used this concept. It did take me a bit of time. But that was the first time ever that I attempted this. This example in this article was the second time I attempted this, and it went much much faster. Why? Because I now know what to look for. There are a few things I want you to keep in mind if you want to try this style of testing:

  1. Don't attempt to start creating context test classes right off the bat. You will most likely fail.
  2. As Robert Martin says, treat your test code as you would your production code.
    • This means that you will refactor your tests as you go along in your development effort.
    • It's during this refactoring that you will begin to see patterns in usage in the data required for your tests.
    • Begin refactoring by simply extracting methods.


  3. Once you begin to see common groupings of functionality, e.g. the same methods being called from various tests or the same data needed by a group of tests, and what not, that's when the creation of context classes will payoff.
  4. Keep these other thoughts in mind when determining the contexts you may need:
    • If tests require the same setup, they belong in a single test class
    • If tests require the same setup as other tests, and also need a few more things (say, more than two..otherwise, just arrange the items in your tests), consider placing them in a nested context class. Remember to make all base classes abstract and the inner-most derived class concrete.
    • If tests require the same setup as other tests, but have additional needed setups different from another nested context classes, create a sibling context class.


I just want to take a minute and explain the last bullet point about sibling contexts. The last point is talking about the same situation we saw even in the small example presented in this post. We had a CategoryTests class that had two nested, sibling context classes: the first nested sibling context class, CategoryEqualityContext required additional setup that CategoryTests did not provide; the second nested sibling context class, ParentCategoryContext, also shared the same setup as CategoryTests but needed additional, yet, different setup than CategoryEqualityContext. Furthermore, the ParentCategoryContext contained yet another nested context class within which required the setups of both CategoryTests and ParentCategoryTest plus additional setup.

Finally, we've come to the end of this very long blog post. I hope that you enjoyed it and that you learned something. If you have a question, feel free to drop me a line and I'll try to get back to you.

Below, find the full, uninterrupted code of the Category class and its corresponding test class(es). Until next time, take care.

using System;
using System.Collections.Generic;
using System.Diagnostices;
using System.Linq;
using System.Text.RegularExpressions;

namespace DDDSportsStore.Domain
{
    [DebuggerDisplay("[Name: {Name}]")]
    public class Category
    {
        private static readonly Category None = new NullCategory();

        private string name;
        private Category parentCategory;
        private List<Category> subCategories;

        protected Category()
        {
            subCategories = new List<Category>();
        }

        public Category(string name) : this(name, Category.None) { }

        private Category(string name Category parent) : this()
        {
            if (string.IsNullOrWhiteSpace(name))
               throw new InvalidCategoryNameException();

            this.name = SanitizeName(name);
            if (parentCategory == null)
                parentCategory = parent;
            else
                SetParentCategory(parent);
        }

        public virtual string Name
        {
            get { return name; }
            set { name = SanitizeName(value); }
        }

        private string SanitizeName(string proposedName)
        {
            return Regex.Replace(proposedName, @"\s+", " ");
        }

        public virtual Category Parent
        {
            get { return parentCategory; }
            set { SetParentCategory(value); }
        }

        private void SetParentCategory(Category c)
        {
            if (c != Category.None && this.HasDescendant(c))
                throw new InvalidOperationException();

            parentCategory = c;
            if (parentCategory == Category.None)
                parentCategory.AddSubcategory(this);
        }

        private bool HasDescendant(Category c)
        {
            bool result = false;

            if (this == c)
                result = true;
            else if (c.Parent != Category.None)
                result = this.HasDescendant(c.Parent);

            return result;
        }

        public IReadOnlyCollection<Category> Subcategories
        {
            get { return subCategories.AsReadOnly(); }
        }

        /// <summary>
        /// Creates a new Category as a subcategory of the current instance.
        /// </summary>
        /// <param name="name">The name of the new subcategory.</param>
        /// <exception cref="InvalidOperationException">
        ///     Thrown if the current instance is <see cref="Category.None"/>.
        /// </exception>
        /// <exception cref="InvalidCategoryNameException">
        ///     Thrown if the <paramref name="name"/> is <c>null</c>,
        ///     the empty string, or consists entirely of whitespace.
        /// </exception>
        public virtual Category AddSubcategory(string name)
        {
            if (this == None)
                throw new InvalidOperationException();
        
            Category subcategory = new Category(name, this);
            AddSubcategory(subcategory);
            return subcategory;
        }

        private void AddSubcategory(Category c) 
        {
            if (this == Category.None)
                throw new InvalidOperationException(
                    "Unable to add a subcategory to the 'None' category."
                 );

            if (c == null || c == Category.None)
                throw new InvalidSubcategoryException();

            if (subCategories.Any(sc => sc.Name == c.Name))
                throw new DuplicateSubcategoryException();

            subCategories.Add(c);
        }

        public bool Equals(Category other)
        {
            if ((object)other == null)
                return false;

            return this.ToString() == other.ToString();
        }


        #region System.Object overrides

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            Category c = obj as Category;
            if ((object)c == nul)
                return false;

            return this.ToString() == c.ToString();
        }

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }

        public override string ToString()
        {
            return Parent == None ?
                Name : string.Concat(Parent.ToString(), "/", Name);
        }

        #endregion

        #region Operator Overrides

        public static bool operator ==(Category a, Category b)
        {
            if (object.ReferenceEquals(a, b))
                return true;

            if ((object)a == null || (object)b == null)
                return false;

            return a.ToString() == b.ToString();
        }

        public static bool operator !=(Categoriy a, Category b)
        {
            return !(a == b);
        }

        #endregion

        #region NullCategory

        /// <summary>
        /// This class reperesents an implementation of the Null Object
        /// pattern (a/k/a Special Case pattern) and is meant to be used
        /// anywhere where you might otherwise return null.
        /// </summary>
        private sealed class NullCategory : Category
        {
            public override Name
            {
                get { return string.Empty; }
                set { }
            }
        }
    }

    #endregion
}

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DDDSportsStore.Domain.Tests
{
    [TestClass]
    public class CategoryTests
    {
        [TestMethod]
        public void Cannot_create_a_Category_with_an_invalid_name()
        {
            AssertCannotCreateCategoryWithName(null);
            AssertCannotCreateCategoryWithName(string.Empty);
            AssertCannotCreateCategoryWithName("\n");
        }

        [TestMethod]
        public void
        A_category_name_is_sanitized_to_contain_only_1_contiguous_whitespace_character_between_words()
        {
            AssertCategoryNameWhitespaceIsCollapsed(
                "A  category  with  extraneous  whitespace",
                "A category with extraneous whitespace"
            );
            AssertCategoryNameWhitespaceIsCollapsed(
                "A\r\ncategory\twith\vother\fwhitespace characters",
                "A category with other whitespace characters"
            );
            AssertCategoryNameWhitespaceIsCollapsed(
                "This category name is ok", 
                "This category name is ok"
            );
        }

        private void AssertCannotCreateCategoryWithName(string name)
        {
            ExceptionAssert.Throws<InvalidCategoryNameException>(
                () => new Category(name)
            );
        }

        private void
        AssertCategoryNameWhitespaceIsCollapsed(string name, string expectedName)
        {
            Assert.AreEqual(new Category(name).Name, expectedName);
        }

        public class CategoryEqualityContext : CategoryTests
        {
            Category categoryA;
            Category categoryB;

            [TestInitialize]
            public void CategoryEqualityContextSetup()
            {
                categoryA = new Category("A");
                categoryB = new Category("B");
            }

            [TestMethod]
            public void
            A_top_level_category_returns_its_name_when_ToString_is_called()
            {
                Assert.AreEqual("A", categoryA.ToString());
            }

            [TestMethod]
            public void
            Can_determine_whether_or_not_two_categories_are_equal_to_one_another()
            {
                Assert.AreEqual(categoryA, categoryA);
                Assert.IsTrue(categoryA.Equals(categoryA));
                Assert.IsTrue(((object)catgeoryA).Equals((object)categoryA));
                Assert.IsTrue(categoryA.Equals(new Category("A")));
            }

            [TestMethod]
            public void
            Can_use_the_equality_operator_to_determine_whether_two_categories_are_equal_to_one_another()
            {
                Category c = new Category("A");
                Assert.IsTrue(c == categoryA);
            }

            [TestMethod]
            public void
            Can_use_the_inequality_operator_to_determine_whether_two_categories_are_not_equal_to_one_another()
            {
                Assert.IsTrue(categoryA != categoryB);
            }
        }

        [TestClass]
        public class ParentCategoryContext : CategoryTests
        {
            private string PARENT;
            private Category parent;

            [TestInitialize]
            public void ParentCategoryContextSetup()
            {
                PARENT = "Parent";
                parent = new Category(PARENT);
            }

            [TestMethod]
            public void Can_add_a_subcategory_to_a_category()
            {
                int numSubcategories = parent.subCategories.Count;
                parent.AddSubcategory("subcategory");
                Assert.AreEqual(numSubcategories + 1, parent.Subcategories.Count);
            }

            [TestMethod]
            public void Cannot_add_a_duplicate_subcategory_to_a_category()
            {
                parent.AddSubcategory("subcategory");
                ExceptionAssert.Throws<DuplicateSubcategoryException>(
                    () => parent.AddSubcategory("subcategory")
                );
            }

            [TestClass]
            public class ChildCategoryContext : ParentCategoryContext
            {
                private string CHILD;
                privte Category child;

                [TestInitialize]
                public void ChildCategoryContextSetup()
                {
                    CHILD = "Child";
                    child = parent.AddSubcategory(CHILD);
                }
    
                [TestMethod]
                public void
                A_subcategory_returns_its_parents_name_with_a_path_separator_and_its_own_name_when_ToString_is_called()
                {
                    Assert.IsTrue(string.Concat(PARENT, "/", CHILD) == child.ToString(),
                        string.Format("Expected '{0}/{1}' but got '{2}'", PARENT, CHILD, child.ToString());
                }

                [TestMethod]
                public void
                Cannot_set_category_parent_if_new_parent_is_a_descendant_of_the_category()
                {
                    Category grandchild = child.AddSubcategory("Grandchild");
                    ExceptionAssert.Throws<Invalidoperationexception>(
                        () => parent.Parent = grandchild
                    );
                }
            }
        }
    }

    public static class ExceptionAssert
    {
        public static void Throws<T>(Action act) where T : Exception
        {
            try
            {
                act();
                Assert.Fail("Expected '{0}' to be thrown but no exception was thrown.");
            }
            catch (T)
            {
                return;
            }
            catch (AssertFailedException)
            {
                throw;
            }
            catch (Exception e)
            {
                Assert.Fail(
                    "Expected '{0}' to be thrown, but '{1}' was thrown instead.",
                    typeof(T).FullName,
                    e.GetType().FullName
                );
            }
        }
    }
}

Saturday, February 1, 2014

Mighty Moose a/k/a ContinuousTests

Introduction

At my current place of employment, I was introduced to NCrunch, a continuous testing solution for .NET and Visual Studio. It’s a great tool that I really enjoy using at work. However, for at home use, I can’t justify paying their asking price for a personal license. Don’t get me wrong, I really love the tool, and if I were doing some serious development on my own (i.e. creating products for sale as a side job), I’d probably pay for it, because it’s fast and shows exactly where an exception occurred or a test failed.

So I really like this concept of continuous testing and went on an Internet search to see what was available out there for free. I wanted something that would run in Visual Studio 2012/2013. A lot of the “free” tools use external test runners which display their output in either the browser (as an XML/HTML page) or in a console window. I don’t like those tools because they interrupt my context, having to switch between Visual Studio and, say, a console window. Plus, then I have to go digging for the file and line number when a test fails.

Enter Mighty Moose a/k/a Continuous Tests

During my search, I found Mighty Moose a/k/a ContinuousTests, by Greg Young. Mighty Moose has a slightly different philosophy about continuous testing. Instead of a code coverage margin in Visual Studio, they have a risk margin. The risk margin shows a colored circle next to a method under test with a number inside. The number inside tells you how many tests are covering the method. The color: red, green, or yellow, indicates the risk of changing that method.

How is risk calculated? It’s based on how close the method was called from the test. According to Greg, while code coverage tells you whether or not the code was executed by a test, that information alone is not enough to classify how risky it is to change the code. What if the code that’s “covered” was called from a method 10 frames down the call stack? By changing that code, you could be affecting not just the method under test, but all 8 other methods in the call stack.

In addition to the risk margin, Mighty Moose contains graph “overlay” graphics (for those of us who can’t afford Visual Studio Ultimate ;) ) that show the call graph of all methods called during the test. This can help you gauge “how far away” a method was invoked from the test, and in general, just help you see the entire call graph, which in itself, could be enlightening.

Getting Mighty Moose to Work with Visual Studio 2013

Mighty Moose is currently at version 1.0.47. It officially only supports Visual Studio 2008, 2010, and 2012. However, there’s an excellent question over on Stack Overflow that I contributed to which shows you how to get Mighty Moose to work with Visual Studio 2013.