dbc

This article is the last of three on Unit Testing and Design By Contract

Testing Post Conditions and Class Invariants

Now we can test the two post conditions of the Add method:

// Count is 0 before adding any items
// This is a post condition of the constructor
//and should be tested separately
[Test]
public void Add_PostCond_CountHasIncreasedByOne()
{
    var stack = new Stack(maximumNumberOfItemsAllowed: 10);
    stack.Add(new object());
    Assert.AreEqual(1,stack.Count);
}
// We use the 'Peek' method to access the item on top of the stack.
// Pre and Post conditions of this method should be tested separately
[Test]
public void Add_PostCond_ItemIsOnTopOfTheStack()
{
    var stack = new Stack(maximumNumberOfItemsAllowed: 10);
    var firstItem = new object();
    var secondItem = new object();
    stack.Add(firstItem);
    stack.Add(secondItem);

    Assert.AreSame(secondItem, stack.Peek());
}

These types of test are probably the most commonly seen. Good Test Driven Development habits include asserting as few times as possible per tests, ideally one assertion only. The point is, as I mentioned earlier, error localization: if you only test one thing at a time, there has to be one test that explicitly exposes a bug.

There is still one more post condition to test: that no invariant rule of the class was violated by the Add method.

Checking Class Invariants

As mentioned earlier, Class Invariants are the rules that should always remain true after any method is executed on a class. The Add method is no exception, so we will have to test that class invariants were enforced when it executed. We don’t want to repeat ourselves, so we will delegate to a reusable method called CheckInvariants that throws an exception if an Invariant rule has been broken.

[Test]
public void Add_PostCond_InvariantsWereEnforced()
{
    //Arrange
    var stack = new MockRepository().PartialMock<Stack>(1);
    stack.Expect(x=>x.CheckInvariants());
    stack.Replay();

    //Act
    stack.Add(new object());

    //Assert
    stack.VerifyAllExpectations();
}

This tests ensures that the method CheckInvariants has been invoked by the Add method. This type of tests, which verifies the interaction of methods rather than the resulting state of objects is known as Behavioural Tests.

The delegation itself is tested by using the Rhino Mocks mocking library for .Net. In this case we create a Partial Mock Stack object. It is partial because we only want to mock one method (CheckInvariants) but we do want to execute a different one (Add) of the same class. We tell the partially mocked Stack that we are expecting the CheckInvariants method to be called later. After we explicitly invoked Add, we tell the stack to verify our expectation. This verification will fail if the method was not invoked indirectly by Add.

How public are the Class Invariants?

So the last thing is to test the Class Invariants. This is just another method called void CheckInvariants() of the class, but its access level deserves a bit of thought.

Let’s see what options we have:

  • private: This method cannot be private because we need to write tests for it (as we did before)

  • protected: This would at least let us create the class TestStack that inherits the Stack class and create a public method that invokes CheckInvariants. See this article for more details on how to mock a protected method

  • internal: Internal methods can be invoked from within the assembly or an external assembly explicitly designated. This is the option chosen in this example

  • public: Not a bad option either in my opinion, any object can invoke this method anytime, which should not cause any inconvenient.

And here are the two test for CheckInvariants itself

[Test,ExpectedException(typeof(InvalidOperationException),
    ExpectedMessage="Count cannnot be negative")]
public void CheckInvariants_PostCond_CountIsNonNegative()
{
    var stack = new MockRepository().PartialMock<Stack>(1);
    stack.Replay();
    stack.Count = -1;

    stack.CheckInvariants();
}
[Test,
ExpectedException(typeof(InvalidOperationException),
    ExpectedMessage = "Count cannot be greater than 7")]
public void CheckInvariants_PostCond_CountIsLessThanMaximumAllowed()
{
    var stack = new MockRepository().PartialMock<Stack>(7);
    stack.Replay();
    stack.Count = 8;

    stack.CheckInvariants();
}

And finally, here is the code for the Stack class, a class that honors its contract:

public class Stack
{
    private int _maximumNumberOfItemsAllowed;
    public Stack(int maximumNumberOfItemsAllowed)
    {
        _maximumNumberOfItemsAllowed = maximumNumberOfItemsAllowed;
    }

    public int Count { get; protected internal set; }

    public void Add(object item)
    {
        if (item == null)
            throw new ArgumentNullException("item");

        if (_maximumNumberOfItemsAllowed == Count)
            throw new InvalidOperationException(
                "Cannot add items when the stack is full.");
        Count++;
        _array.Add(item);

        CheckInvariants();
    }

    private IList _array = new ArrayList();

    public object Peek()
    {
        return _array[Count - 1];
    }

    protected internal virtual void CheckInvariants()
    {
        if (Count < 0)
            throw new InvalidOperationException("Count cannnot be negative");

       if (Count > _maximumNumberOfItemsAllowed)
            throw new InvalidOperationException("Count cannot be greater than " + _maximumNumberOfItemsAllowed);
        }
    }