Unit Test & Design By Contract (Part III)
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 theStack
class and create a public method that invokesCheckInvariants
. 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);
}
}