Test Driven-development can be approached using at least two strategies, a mock based approach or a state based approach (Martin Fowler calls this a classicist approach http://martinfowler.com/articles/mocksArentStubs.html). When presented with the initial idea, TDD seems simple; write the test to verify the behaviour code you are about to write. A common example of this is a calculator.
Say we have a calculator class that has the following method:
public void int Add(int integer1, int integer2)
We may write a test to verify this works first;
public void Should_Sum_inputs_together()
{
var result = new Calculator().Add(1,4);
Assert.AreEqual(5,result);
}
This is probably the defacto example of a state based testing approach. An action is performed on a class, and then its state is interrogated or a return value is verified. In many introductions to TDD you will find trivial examples such as this one. When test-driving code ‘in the wild’ you soon find that certain things become more complex very quickly.
Mock based TDD
Continuing on with the calculator example we can see where state based testing alone may not be enough.
Say we move a little closer to the GUI and have a ButtonInterface class that implements the interface,
IButtonInterface
{
Button Zero;
Button One;
…
Button Nine;
Button Add;
Button Equals;
void PressButton(Button button);
}
Our test may be :
public void Should_Sum_inputs_together()
{
//mock a calculator -- I’m using pseudo code based on Moq
var mock = new Mock
ICalculator calc = mock.Object;
IButtonInterface calcUI = new ButtonInterface(calc);
calcUI.PressButton(this.One);
calcUI.PressButton(this.Add);
calcUI.PressButton(this.Four);
calcUI.PressButton(this.Equals);
mock.Verify(c => c.Add(1,4));
}
When testing the button interface we do not need to know how our class that does the addition manages the task. It is not the responsibility of the button interface to do so. However it is the responsibility to take the button inputs and send the appropriate messages to the calculator.
This is where mock based testing comes in. If the ICalculator interface hasn’t been written yet or perhaps there is a cost in setting up an ICalculator, for example it may be dependant on a filesystem or a database. In these cases tests may run slowly due to getting the dependancy into the correct state and then resetting it afterwards. In these examples and the more striking fact the the ButtonInterface doesn’t care all we need to verify is that the correct message has been sent to the correct interface.
1 comment:
I tend to use the terms 'statist' and 'behaviourist' for the two schools of thought for testing (these are also the terms used in the book xUnit Test Patterns). I also believe that a good testing approach dosen't go for one or the other, and instead walks the middle ground of using whichever best expresses the test intention. As pointed out, behaviourist testing is very appropriate for GUI components driving some form of 'engine', where the important thing isn't the exact result of the call, but merely that the call has occurred (and has occurred in the correct fashion, and potentially in the correct order). This is also potentially true of any intra-component testing to an interface, which is where I tend to make the heaviest use of mocks, stubs and fakes :)
Post a Comment