Deep Dive into TDD Revisited

Hi, everyone. I haven’t posted any serious technical content on this blog for a long time now. The reason for this is that I’m now a pointy haired boss most of the time. I spend my days teaching, mentoring, coaching, and occasionally pairing with someone on another team. I miss coding… I really do.

However, I’ve been digging into Interaction Based Testing over the past few weeks, and I’ve found it fascinating. The road I took to get here involved trying to learn more about what Behavior Driven Development is, and why so many people I know and respect seem to like it, or at least appreciate it. One of the techniques that BDD uses is something called Interaction Based Testing, or IBT for short.

Interaction Based Testing

IBT is different from traditional TDD in that it is defining and verifying the interactions between objects as they take place, rather than defining and verifying that some input state is being successfully translated to some output state. This latter kind of testing, called State Based Testing, or SBT for short, is what I had always done when I did TDD (for the most part). IBT involves using a Mock Object Framework that allows you to set expectations on objects that your class under test is going to call, and then helps you verify that each of those calls took place. Here is a short example:

[TestFixture]
public class IBTExample
{
    [Test]
    public void SampleITBTest()
    {
        MockRepository mocks = new MockRepository();

        IListener listener = mocks.CreateMock<IListener>();
        Repeater repeater = new Repeater(listener);

        listener.Hear("");
        LastCall.On(listener).Constraints(Is.NotNull()).Repeat.Once();

        mocks.ReplayAll();

        repeater.Repeat("");

        mocks.VerifyAll();
    }
}

The basic problem that I’m trying to solve here is that I can write a method, Repeat(), on a class called Repeater such that when I call Repeat(), it repeats what it was passed to its IListener. The way that I set this up is more complicated than I would use in a state-based test, but I avoid cluttering my test with irrelevant implementation details (like explicit data).

What this test is doing is creating the system and setting expectations on the IListener that define how the Repeater class is going to use it at the appropriate time. The MockRepository is the class that represents the mock object framework I’m using, which in this case is Rhino Mocks. I new one of these up, and it handles all the mocking and verification activities that this test requires. On the next line, you see me creating a mock object to represent an IListener. I typically would have created a state-based stub for this listener that would simply remember what it was told, for my test to interrogate later. In this case, the framework is creating a testing version of this interface for me, so I don’t have to build my own stub. Next, I create the class under test and wire it together with the listener. Nothing fancy there.

The next line looks a little strange, and it is. It is actually a result of how this particular mocking framework functions, but it is easily understood. While it may look like I’m calling my listener’s Hear method, I’m actually not. When you create an instance of the mocking framework, it is created in a recording mode. What this means is that every time you invoke a method on a mocked out object while recording, you are actually calling a proxy for that object and defining expectations for how that object will be called in your regular code later. In this case (admittedly, not the simplest case), listener.Hear() is a void method, so I have to split the setting of expectations into two lines. On the first line, I call the proxy, and the framework makes a mental note that I called it. On the next line, I say to the framework, “Hey, remember that method I just called? Well, in my real code, when I call it, I expect that I am going to pass it some kind of string that will never be null, and I’ll call that method exactly once. If I do these things, please allow my test to pass. If I don’t do them, then fail it miserably”.

After I set up the single expectation I have on the code I’m going to be calling, I exit record mode and enter replay mode. In this mode. the framework allows me to run my real code and plays back my expectations for me while my real code executes. The framework keeps track of whatever is going on, and when I finally call my application method, Repeater.Repeat() in this case, followed by the mocks.VerifyAll(), it checks to make sure that all expectations were met. If they were, I’m cool, otherwise my test fails.

I hope that was at least a little clear. It was very confusing to me, but I sat down with a few folks at the agile conference two weeks ago, and they showed me how this worked. I’m still very new at it, so I’m likely to do things that programmers experienced with this kind of testing would find silly. If any of you see something I’m doing that doesn’t make sense, please tell me!

Here is the code this test is forcing me to write:

public class Repeater
{
    private readonly IListener listener;

    public Repeater(IListener listener)
    {
        this.listener = listener;
    }

    public void Repeat(string whatToRepeat)
    {
        listener.Hear(whatToRepeat);
    }
}

public interface IListener
{
    void Hear(string whatToHear);
}

Advantages to IBT style TDD

There are several things about this that I really like:

  • It allows me to write tests that completely and totally ignore what the data is that is being passed around. In most state-based tests, the actual data is irrelevant. You are forced to provide some values just so that you can see if your code worked. The values obfuscate what is happening. IBT allows me to avoid putting any data into my tests that isn’t completely relevant to that test, which allows me to focus better on what the test is saying.
  • It allows me to defer making decisions until much later. You can’t see it in this example, but I’m finding that I’m much better able to defer making choices about things until truly need to make them. You’ll see examples of this in the blog entries that are to follow (more about this below).
  • I get to much simpler code than state-based testing would lead me to
  • My workflow changes. I used to
    1. Write a test
    2. Implement it in simple, procedural terms
    3. Refactor the hell out of it

With ITB, I’m finding that it is really hard to write expectations on procedural code, so my code much more naturally tends to lots of really small, simple objects that collaborate together nicely. I am finding that I do refactoring less frequently, and it is usually when I’ve changed my mind about something rather than as part of my normal workflow. This is new and interesting to me.

There are some warts that I’m seeing with it, and I’ll get to those as well, as I write further in this series. I’m also very certain that this technique has its time and place. One of the things I want to learn is where that time and place is. Anyhow, here are my plans for this:

Revisiting my Deep Dive

I want to redo the example I did a couple years ago when I solved the Payroll problem in a 6-part blog series. I want to solve the same problem in a ITB way, and let you see where it leads me. I’ve done this once already, part of the way, just to learn how this worked, and the solution I came up with was very different than the one I did the first time. I’m going to do this new series the exact same way as the old series, talking through what I’m doing and what I’m thinking the whole time. I’m personally very curious to see where it goes.

Once we’re finished, I want to explore some other stories that are going to force me to refactor some of my basic design assumptions, because one of the knocks against ITB is that it makes refactoring harder by defining the interactions inside your tests and your code. We’ll find out.

Please ask questions

I’m learning this stuff as I go, so I’m very eager to hear criticisms of what I’ve done and answer questions about why I’ve done things. Please feel free to post comments on the blog about this and the following entries. I’m really looking forward to this, and I hope you readers are, too.

— bab

7 thoughts to “Deep Dive into TDD Revisited”

  1. With a void method like this, called for its side effect, is it even possible to do SBT?

    I’m not sure you can say that you get to ignore the data that gets passed around. You did pass in an empty string to the Repeat method, and checked that it made it to the Hear method. What if the Repeat method reacted differently to empty strings? Then you’d have to test using 2 different sets of inputs — 1 empty, and one non-empty.

    I think it’s clear that you have to have some state-based testing still in at least some cases. For example, my model classes have restraints on properties. Like the user ID should be between 6 and 12 characters. Is there some way to test that via IBT? I’m not able to think of a way. Perhaps my conclusion is that things that primarily maintain state should use SBT, and things that are used primarily for their interactions (including side effects) should us IBT. What else does that leave? Functions that take an input and return an output — I suppose those could go either way, but I’d lean toward SBT, passing something in and seeing what comes out.

    I suppose we’re really talking about black-box testing versus white-box testing here. I found a couple articles on agile methods and white-box testing; maybe they’ll shed some light.

    I wish you were using a more readable language and mocking framework. The syntax is getting in the way quite a bit (like having an implicit recording section). In RSpec the 8 test lines would become 4:

    listener = mock(‘Listener’)

    listener.should_receive(:hear).once.with(”)

    repeater = Repeater.new(listener)

    repeater.repeat(”)

    Which is almost as clear as what I think the test REALLY means:

    Repeater(:listener).should_implement(:repeat,:r).as(‘listener.hear(r)’)

    (Yeah, I’m still not quite ready to fully buy into IBT.)

  2. Craig,

    I agree that state-based testing is entirely relevant still. It has its place at the leaf nodes in the tree.

    Read my next post for more info. Coming up in about 5 minutes.

    bab

  3. Hi.

    (btw, seems like I can’t post comments with Firefox)

    If you don’t care about data, why have parameters in the first place?

    One problem I have with these kinds of tests, ok, two problems:

    1. They are strange, indeed. I struggle to convince my coworkers this is worth doing.

    2. How do I test the correct call when the instance passed is not important, but what it contains?

    A small example on my 2nd point, I want to know if the passed Credential is correct:

    IView { string Name; string Password; }

    IAuthenticator { bool Authenticate(Credentials); }

    private void SignIn_Click(…) {

    Credentials c = new Credentials(view.Name, view.Password);

    if (authenticator.Authenticate(c)) {…}

    }

  4. Thomas,

    I share your concerns about this. These tests don’t have the traditional 3-A’s organization, and they do look strange. And it is completely and totally possible that I’m using these things wrong, and they’re even stranger because of it 🙂

    As to your second point, this is one of the strengths of this way of writing tests, if I understand what you were asking.

    It is not the responsibility of this class to know how authentication happens. What we can do is to mock out the IAuthenticator.Authenticate() call, force it to return true or false, and set expectations on the different behavior of your signin_click method based on that.

    This part of it I kind of like. It really does force out small classes and methods early, while I’m writing the tests.

    Did that address your concern?

    — bab

  5. Hi. What’s wrong with the commenting system? I can swear I posted this…

    Anyway, I agree with you on mocking out the IAuthenticator. But we still have to verify it’s used properly.

    Of the following:

    a.Authenticate(null);

    a.Authenticate(new Credentials(null, null));

    a.Authenticate(new Credentials(v.Name, v.Password));

    All is possible, but only one is correct. How do I write an IBT to verify?a

  6. > Comments from Brian Button – One Agile Coder:

    >

    > Sender: Thomas Eyde

    > Url: IP Address: 62.249.170.60

    > =====================================

    >

    > re: Deep Dive into TDD Revisited

    >

    > Hi. What’s wrong with the commenting system? I can swear I posted this…

    I know, I’m having problems, too. The three other guys I’m sharing the site with are considering other options as we speak…

    >

    > Anyway, I agree with you on mocking out the IAuthenticator. But we still have to verify it’s used properly.

    >

    > Of the following:

    > a.Authenticate(null);

    > a.Authenticate(new Credentials(null, null));

    > a.Authenticate(new Credentials(v.Name, v.Password));

    >

    > All is possible, but only one is correct. How do I write an IBT to verify?a

    Ah, interesting question. I’m still struggling with when and how to use specific arguments in an IBT. It almost seems like you want to NOT use data in them, to let you focus on control flow and data flow (without caring about the data itself). My current idea is that you use state-based testing when you want to verify values and IBT when you want to flesh out flow. The examples on the web I’ve seen, however, seem to use values in their expectations in some way, and I’m still trying to figure out when that is appropriate.

    For my first attempt, I’m trying to turn the knobs all the way up and avoid values for as long as I can, just to see where that leads. I’m trying to do IBT all the way through the call graphs, and then drop back to SBT at the leaf methods. We’ll see where that takes me.

    I’d love to hear more of your opinions on this, as you seem to have a bit more experience than I at this 🙂

    bab

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.