driving the design of interfaces

How do you drive the design of your interfaces?

That is a question posed by Brett Schuchert from a twitter discussion and he illustrates a way of driving the design in a test-driven fashion.

As I'm into my context specifications in a big way at the moment I thought it would be interesting to explore how I might drive the same small piece of code from a blank slate.
I'm using the developwithpassion library and am in the middle of a series on my use of context specification that has more detail on my use of the library and how it works.
So here goes:

First, I'd create a new file to contain my specification and call this GameSpecification. I'd then write the context as:

using developwithpassion.bdd.mbunit.standard.observations;

namespace Driving.Interfaces.Example
{
    public class when_initialising_the_hardware_layer_during_game_construction : 
                                observations_for_a_sut_without_a_contract<Game>
    {
    }
}

So I have my context and my system under test is a Game class which doesn't exist so the code doesn't compile. Using Resharper I create the Game class in the same file:

using developwithpassion.bdd.mbunit.standard.observations;

namespace Driving.Interfaces.Example
{
    public class when_initialising_the_hardware_layer_during_game_construction : 
                                observations_for_a_sut_without_a_contract<Game>
    {
    }

    public class Game
    {
    }
}

According to the original post we know up front that Game uses the Hardware Layer. So next step is to put that dependency on using my context - the framework I am using can auto-wire up mocks for any constructor dependencies and I can access them using some prepared syntax:

public class when_initialising_the_hardware_layer_during_game_construction : 
                                observations_for_a_sut_without_a_contract<Game>
{
    private static IHardwareLayer hardwareLayer;

    private context c = () => hardwareLayer = the_dependency<IHardwareLayer>();
}

Again, this does not compile and so I use Resharper once again to construct an interface within the same file.
I now add the constructor on the Game class:

public class Game
{
    public Game(IHardwareLayer hardwareLayer)
    {
    }
}

Now I'm ready to make my observations:

public class when_initialising_the_hardware_layer_during_game_construction : 
                            observations_for_a_sut_without_a_contract<Game>
{
    private static IHardwareLayer hardwareLayer;

    private context c = () => hardwareLayer = the_dependency<IHardwareLayer>();

    it should_set_the_resolution = () => 
        hardwareLayer.was_told_to(layer => layer.SetResolution(expectedWidth, expectedHeight));
}

I have delcared what I expect to be called on the IHardwareLayer interface - a SetResolution method with specific values (that are not yet defined).
First, I define those expected values:

public class when_initialising_the_hardware_layer_during_game_construction : 
                            observations_for_a_sut_without_a_contract<Game>
{
    private static IHardwareLayer hardwareLayer;
    private static int expectedWidth;
    private static int expectedHeight;

    private context c = () =>
                            {
                                hardwareLayer = the_dependency<IHardwareLayer>();
                                expectedWidth = 1024;
                                expectedHeight = 768;
                            };

    it should_set_the_resolution = () => 
        hardwareLayer.was_told_to(layer => layer.SetResolution(expectedWidth, expectedHeight));
}

Notice that my private variable declarations are just that - declarations. I keep the "Arrange" part of my AAA syntax all within the context delegate so the expected values are assigned there.

Now for the interface modification; again I use Resharper and quickly create that method:

public interface IHardwareLayer
{
    void SetResolution(int widthInBits, int heightInBits);
}

I now have an observation via my "it" delegate and I can run this test and see that it fails the expectation on the mock object with a Rhino.Mocks.Exceptions.ExpectationViolationException.
Implementing this code I am first careful about the implementation - I want to set the resolution, but I want to set it to a pair of values that should fail the test. In this way I don't get a false positive test that doesn't actually verify the values as I'd expect.

public class Game
{
    public Game(IHardwareLayer hardwareLayer)
    {
        hardwareLayer.SetResolution(1000, 1000);
    }
}

When I run the test again it still fails as the values are explicitly checked - great. Then I implement the real values in the Game constructor and the test passes.

The above process shows how I drive the interface and the implementation step-by-step. It is geared towards my use of the library and tools that I am using, but I don't think that is a bad thing... leveraging the tools at my disposal allows me to work in this context driven way that is fast and more importantly very expressive.

On reflection I would say there is something missing in the AAA syntax above - I have an "arrange" context delegate and an "it" assertion, but there is no "act" that would normally be in a "because" delegate (see my previous posts for more detail). This would suggest that due to the "act" part being within the constructor, the design might need to be looked at, but this exercise has been to use the code from Brett's post and just see what it looks like in context specification.



0 comments: