Writing maintainable unit tests

The project I am currently working on has about seven-hundered unit tests. On previous projects I have worked on, there have been as many as two-thousand tests. Unit tests are definitely not just for Christmas, they are for the lifetime of the code base! They have a purpose and that is to minmise bugs being checked into source control. Unit tests are also meant to act as documentation of the code. I have a rule that if I cannot understand what a particular unit test is trying to do in thirty seconds, then it needs to be refactored! Generally the tests that I cannot make sense of are due to noise around the test and the test class itself.

This blog post aims to introduce three guidelines to help improve the maintainability and readability of your unit tests, by

  1. altering the layout of your test classes
  2. reducing common noise
  3. and improving readability

I have introduced these three guidelines recently into my team and have found that the quality and understanding of each test has greatly improved, in particular when refactoring or fixing bugs in existing classes.


1) Structuring Unit Tests
When unit testing properly, most public methods within an application will have multiple unit tests associated to it. These tests should cover the different outputs to a method based usually on a different set of input data. Given a class under test with a lot of public methods, the test class can quite often become quite overloaded and quite difficult without close inspection to see what each test method is actually testing, i.e. the method it is testing and the scenarios for which it is under test.

With this in mind I recently read a post by Phil Haack detailing a solution he had come across to improve the readability of what each test class is actually doing. The solution is simple. For each public method you are testing, in your unit test class, create a nested class to encapsulate all the test methods for a given method. This is effectively grouping together tests by public method. This may seem like overkill at first, but to avoid repetition, you can define the setup and teardown methods in the base class. This means all the nested classes can inherit the base to reuse anything that should be shared.

The following example below shows the basic structure of a test class, which tests two public methods, the first called Get, the second Post.


    [TestClass]
    public class BlogPostTaskTests
    {
        protected Mock<IBlogPostRepository> blogPostRepository;

        [TestInitialize]
        public void Setup()
        {
            //shared setup, generally just initialising mocks
        }

        [TestClass]
        public class TheGetMethod : BlogPostTaskTests
        {
            [TestMethod]
            public void HappyPath()
            {
                //arrange
                //act
                //assert
            }

            [TestMethod]
            public void UnhappyPath()
            {
                //arrange
                //act
                //assert
            }
        }

        [TestClass]
        public class ThePostMethod : BlogPostTaskTests
        {
            [TestMethod]
            public void HappyPath()
            {
                //arrange
                //act
                //assert
            }

            //Additional Tests for the Post method here
        }

        [TestCleanup]
        public void Cleanup()
        {
        }
    }



2) Use the Test Data Builder Pattern for test data
Most unit tests require some input data for a method to test, and some output data to assert. I have found it too commonly the case that developers will duplicate instances of objects across multiple test classes, and initialise the object with hard coded test values. All this set up code within a test class adds a lot of noise to test classes which can make it difficult to understand what the test is doing.

Given the following class:

public class BlogPost
{
    public int Id { get; set; }

    public string Title { get; set; }

    public string Content { get; set; }
}

All to commonly you will find scattered across many test classes an instance of this class being initialised with test values to be used in a test.

var blogPost = new BlogPost
{
    Id = 1,
    Title = "My Title",
    Content = "My Content"
};

Following the DRY (Don’t Repeat Yourself) principle, I would recommend having a look at the test data builder pattern. The code below is an example of this for the BlogPost class. The class follows a number of conventions for consistency

  • Create a class for each domain object. The name of the class follows [DomainName]Builder
  • A private member variable for each property on the underlining class is added. This variable is assigned a default value.
  • To allow you to override a default value, provide a method with the name With[PropertyName]. This method follows a fluent style to allow you to chain methods together. Only add the With methods when you need them. 95% of the time the default value will probably suffice, there is no value in adding code that is not ever executed.
  • Add a Build method. This method will create and return a new instance of the object with either the default or overrided values.
public class BlogPostBuilder
{
    private int id = new Random().Next(1000);
    private string title = "My Title";
    private string content = "My Content";

    public BlogPostBuilder WithTitle(string title)
    {
        this.title = title;
        return this;
    }

    public BlogPost Build()
    {
        return new BlogPost
        {
            Id = id,
            Title = title,
            Content = content
        };
    }
}

An example of using the builder class

 var blogPost = new BlogPostBuilder()
                                    .WithTitle("Another Title")
                                    .Build();

Each unit test method should be responsible for initializing its own test data opposed to initializing the test data within the test setup. This makes it clear what data each unit test it utilising.


3) Use unit testing extension plugins to improve readability
I have recently been looking a very useful library called Fluent Assertions which provide extensions on top of unit testing frameworks such, with the aim of improving readability by following a more natural language, and reducing the amount of code that is required.

Fluent Assertions is effectively a set of extension methods on top of commonly used unit testing frameworks such as NUnit and MSTest.

As a simple example you could take the following Assert statement:

Assert.IsTrue(actualOutcome > 5);

and rewrite it as the follows:

actualOutcome .Should().BeGreaterOrEqualTo(5);

This is a simple example, but Fluent Assertions provides a wealth of extensions.

You can download Fluent Assertions via NuGet with the following command from the package manager console within Visual Studio.

INSTALL-PACKAGE FluentAssertions

and you can read about it here


I would recommend next time you write some tests, try following the three guideline above, I guarentee it will improve the maintainability and readability of your tests.

I would be very interested to hear any guidelines you follow to improve your unit tests. Thanks for reading.

2 thoughts on “Writing maintainable unit tests

  1. Pingback: Five Blogs – 27 March 2012 « 5blogs

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>