Unit test patterns.

Developers who have incorporated unit testing into their development process already know its advantages: cleaner code, courage to refactor, and improved productivity. Writing unit tests is easy; it is writing good tests that makes automated unit testing and test driven development powerful tools. This article provides some basic testing patterns that help write good tests.

What makes a great unit test

A great unit test will do the following:

  • Fail only when a bug has been introduced
  • Fail Fast, for instant feedback
  • Easy to understand what scenario is being tested and needs fixing

Arrange Act Assert (AAA) Pattern

Modern tests contain three parts.

[TestMethod]
public void GetCount_ItemCountIsZero_NoNewMessages()
{
    //Arrange
    Mailbox mailbox = new Mailbox();

    //Act
    var result = mailbox.GetCount(0);

    //Assert
    Assert.AreEqual("No new messages.", result);
}
  • Arrange: setup everything needed for the running the tested code. This includes any initialization of dependencies, mocks and data needed for the test to run.
  • Act: Invoke the code under test.
  • Assert: Specify the pass criteria for the test, which fails it if not met.

This pattern is very readable and consistent. It’s very easy to identify the different parts of the test, and to create tests for other scenarios from a root test.

State-based tests Pattern

Usually, the code results in a state change, which is exposed by the object. We can look at state using:

  • Values directly returned from the method
  • Values exposed through the object fields
  • Values exposed through other methods or properties of the object
  • Values that come from outside the object, for example static state or a shared data structure.

Tests that assert these values are called state-based tests. Depending on the test framework of choice, you’ll find Assert APIs that let you compare these values to an expected set. In this example, we’re using MS-Test framework’s Assert.AreEqual to compare a returned value from method.

[TestMethod]
public void GetCount_ItemCountIsZero_NoNewMessages()
{
    //Arrange
    Mailbox mailbox = new Mailbox();

    //Act
    var result = mailbox.GetCount(0);

    //Assert
    Assert.AreEqual("No new messages.", result);
}

State-based tests are usually more robust than their interaction-based counterparts. They don’t increase coupling between the test and code in the Assert part, since the assertion is made on data that is exposed through public interfaces. There’s no risk of tests breaking because of refactoring the internal implementation of methods.

This also adds to their readability: The tests describe behavior through public interfaces, without exposing internal mechanisms.

Mock objects/Fake Objects Patterns

When unit testing, often the tested code interacts with external dependencies. These dependencies can be other objects in the system, files, databases, frameworks or 3rd party libraries. In order to speed up our tests, and control the behavior of the dependencies, we would prefer to use mock objects.

Mock (or fake) objects can be hand-written, or supplied by a mocking framework .

In the following example, the method under test GetCountFromProvider, has a dependency on an IMessageProvider interface:

public string GetCountFromProvider(IMessageProvider MessageProvider)
{
    if (MessageProvider.Count == 0)
    {
        return "No new messages.";
    }
    else
    {
        return "New messages: " + MessageProvider.Count.ToString();
    }
}

IMessageProvider has a read-only Count property. In order to test the method in different cases, we’ll need to change the returned value from that property.

We can define a fake message provider object that we can control. We can set the Count value, which is part of the original IMessageProvider interface, using SetCount method:

public class FakeMessageProvider : IMessageProvider
{
    int count;
 
    public int Count
    {
        get
        {
            return count;
        }
    }
 
    public void SetCount(int newValue)
    {
        count =  newValue;
    }
}

Then our test will look like this:

[TestMethod]
public void GetCountFromProvider_ItemCountIsZero_NoNewMessages()
{
    Mailbox mailbox = new Mailbox();
    FakeMessageProvider fakeProvider = new FakeMessageProvider();
    fakeProvider.SetCount(0);
 
    var result = mailbox.GetCountFromProvider(fakeProvider);
    Assert.AreEqual("No new messages.", result);
}

The test creates an instance of the FakeMessageProvider, initializes it, then passes it as an argument. While using handwritten mocks works, they get really complex, as the tested code grows complex. The result is hard to maintain fake code, which may also have bugs in it.

The solution is to use a mocking framework, like Typemock Isolator. Isolator creates the fake objects, and we need to specify the behavior of those objects. With Isolator, the test looks like this:

[TestMethod]
public void GetCountFromProvider_FakeItemCountIsZero_NoNewMessages()
{
    Mailbox mailbox = new Mailbox();
    IMessageProvider fakeProvider = Isolate.Fake.Instance();
     
    var result = mailbox.GetCountFromProvider(fakeProvider);
    Assert.AreEqual("No new messages.", result);
}

Isolator creates the fakeProvider, which implements the original interface. The fakePrivder’s Count property is already set to zero by default, so there’s no need to even set it to return that value.

Sometimes, however, our code depends not only on objects that are passed as arguments, or set through the tested code interface. For example, if our code depends on static data:

public string GetCountFromCenter()
{
    if (MessageCenter.Count == 0)
    {
         return "No new messages.";
    }
    else
    {
         return "New messages: " + MessageCenter.Count.ToString();
    }
}

Here the tested code relies on the MessageCenter.Count static property. Mocking can help here as well.

[TestMethod]
public void GetCountFromCenter_ItemCountIsZero_NoNewMessages()
{
    Mailbox mailbox = new Mailbox();
    Isolate.WhenCalled(() => MessageCenter.Count).WillReturn(0);
     
    var result = mailbox.GetCountFromCenter();
    Assert.AreEqual("No new messages.", result);
}

In this case, we use Isolator to specify the behavior of the Count property, in the same way we do for objects.

Tests that use mocking are fast, since they replace slow behavior (calling the file system, calling the cloud) with in-memory operations. They are focused since they neutralize the dependency effects on the tested code, and therefore also make the tests deterministic.

Using mocks require coupling to the code. Readability of the tests therefore relies on how the code under test looks like. The more complex it is, the less readable the test is.

That are not all patterns that exist in unit testing, so later we will look at other.

Used resource: Unit Test Patterns

Share this post:Tweet about this on TwitterShare on Facebook0Share on LinkedIn0Share on Google+0Share on Reddit0Email this to someoneDigg this