Monday, 24 August 2009

Collar Interfaces

In working on a recent programming contract, we were using test-driven development (TDD) with a mocking framework. The test calls a method of the object under test but doesn't isolate the method from the objects that it calls. This is the input interface to the class. It defines the way that we will use the object. References to objects that it uses may be passed to the object but that doesn't define the expected behaviour of these objects that the object under test is using.

Mocking requires that you consider the dependent object behaviour. The mock object creates a replacement for objects used by the object under test and defines the expected responses of that object without actually creating the object. Using the mock object, we define what method we expect the object under test to call, the parameters that we expect to be passed, and the any expected return value. The result is that we verify the operation of our object independent of other objects that it might use. As long as the authors of dependent objects agree to the interfaces and behaviours that we have tested to, we should be able to plug the objects together and have an operational system.

In effect, the combination of TDD and mocking has defines both the input (usage) interface and the dependencies interface for the object being tested. The difficulty is that the object / class doesn't carry that information with it. We have to go to our tests to determine the required behaviours.

One of the people that I interviewed for my PhD thesis (Thompson 2008) talked about how with hardware these dependencies were defined in what is called a collar interface. He suggested that a collar interface should be defined for objects. My experience with TDD and mocks suggests that a collar interface definition would really help. The mock is looking to ensure that the objects downstream dependency relationships are working as expected. With these defined as part of the collar interface, it would become easier for the programmer to write test cases for objects.

There is another aspect of using mocks. When you you test-drive development from the calls and return values, you ignore the downstream effect other than ensuring that the dependency objects are in place. If there are too many downstream dependencies then it can be difficult to set up the conditions for the test. There can also be a lot of side effects that are difficult to control.

Using a mocking framework removes the need to create all the downstream dependencies and how to simplify them. The programmer doesn't simply define the call and expected return values. In defining the mock objects, they define precisely what methods they expect to call, the parameter values that are expected on those calls, and the values to be returned. That id they have defined the complete collar interface for the method under test.

The mock expectation and return should / could be used as test drivers for the classes being mocked out. None of the tools that I have used so far do this type of verification. It in theory could generate test stubs from the mock definitions. After all, these are the behaviours that our system is expecting of the dependent objects.

Taking this thinking, it seems possible that tables of calls and expectations could be used to build tests with mocked dependencies. If the full collar interface was defined for a class, generating the tests for these dependency chains may be easier to achieve.

Reference

Thompson, E. (2008). How do they understand? Practitioner perceptions of an object-oriented program. Massey University, Palmerston North.

No comments: