Saturday, June 9, 2007

I taught my husband TDD today

(Scott left a comment that made me edit this post a bit)
In less time than it took me to write this post. Can you believe that?? He learned to drive design. He got it!

We started our lesson when I was writing another post about TDD (big surprise, right?). He said I should post some examples. I said that my examples wouldn't be useful without the reactions of the audience. Everyone learns differently.

He gave me a simple example to do, test drive a class that writes "Hello World" to the console. I started explaining what makes this simple problem not so easy to test and we decided to try out the example together.

Asking questions is a great tool to help someone learn, it makes the learner realize conclusions. Figuring out the right questions is always difficult and this morning was no different.

I asked lots of questions. They were focused on one thing: How can I make the introduction of a mock organic?

Eventually he suggested we put Console.Writeline in our CUT, then we refactor it to be testable.

This made me realize my questions were not right.

I needed to teach him how to think in the test. I knew this was a major key how I learned TDD, but I thought it was an evolution.

I was wrong.

In this moment, I realized that learning TDD isn't about writing tests. Its about leveraging polymorphism to verify responsibilities and interactions.

Once I realized this, I only needed to ask one question:
Is there a way we can put something between our CUT and the System.Console to verify the right method was called?

In a few minutes we had an IConsole and a MockConsole implementation. A few minutes later we had a working test, a minute after that we had a console application that wrote hello world.

During his implementation of MockConsole, he found many limitations. After we had a working application, we went back in the test and replaced MockConsole with a RhinoMock.

In the end, he understood how the test drove his design, why it did, why he used a mock and appreciated a mocking framework.

It was awesome.

I'll let his reflection on his learning speak for itself:
I really like TDD. The code you end up with is really good. Its the minimum amount of code to write to make your code testable. The difficulty between just programming and test drivent development is very different...

On the first day of college, you write hello world straight to the console without any understanding of coding, its the real basics.

On your first day of TDD it takes a few layers in between to do it right, its geometrically more complicated, but its only adding a some subtle difference, like IConsole. It gives you the round trip of object oriented programming with simple examples that really illustrate why you do the things you do with object oriented programming. It really takes a great understanding of OOD to appreciate TDD and do it right.

Deciding what tests to write and how you write them are hard, harder than writing the code.

Thank you, that was nice.

13 comments:

sbellware said...

Pairing shouldn't be about talking someone through the design. Talking someone through design, and the sheer desire to be talked through a design or an approach rather than cooperatively surfacing and refining it is yet another kind of waterfall thing.

Design in micro increments can't be disembodied and transmitted independently of the implementation exercise. That kind of thing is the predisposition of phased-based, chunky design approaches.

Next time, you should be firm with him. Either he accedes to his own novice-ness and submits to your agile authoritay, or no pairing for a week :)

wunda said...

Scott,

Thanks for this comment, it made me realize that this post wasn't illustrating what I wanted it to -- a different way for me to approach teaching tdd and how well it worked.

Bill said...

I liked this post. Any way of saying the same lesson from a different point of view is helpful. I'm still struggling in the solution.

Have you ever webcast any of your TDD teachings? I would really be interested in that!

Edward said...

I am interested to know what the tests looked like both before and after introducing RhinoMocks.

I am also interested in the test names - they seem to somehow drive the tests in many of the examples I see.

I am so close to 'getting' TDD and each example I see moves me one step closer.

Russ C. said...

This is an interesting blog, thanks Wunda!

I too am interested in TDD but not really being in an environment where that kinda things encouraged (everyone talks about how cool it is, but no-one does it!) I really have a hard time visualising the process.

Could we see your code for this, perhaps you could compose a blog entry narrating the process that you and your extremely lucky Husband went through!

Thanks in advance.

Francesco said...

You guys are just amazing... I know this comment really doesn't add to the conversation but I had to say this out loud :)

wunda said...

Bill,

When I was teaching my husband I did think our interaction would have been very interesting to post. Maybe next time I will do that.

wunda said...

Edward,

The test looked very similar except:

1. Construction of our mock class instead of the Rhinomock
2. Expectation of the RhinoMock before executing the helloworld method
3. Using an Assert in our mock class to verify instead of Verify(mock)

When we first started, I spent a good deal of time discussing the name, how it should address what we're actually testing, etc. You know what? It didn't make any sense to start like that...

Names help you know what you're testing and understand a classes responsibilities, but you need to understand testing well to figure out what names help you most. Our test was called Test.

wunda said...

Russ,

You're not alone, visualizing the process is really, really hard. I learned by practicing (a lot) on my own and it was painful.

If its something thats encouraged in your environment, see if your company will bring in an expert to get your team started with testing.

Kris said...

TDD isn't easy. I thought I got it but that was when my team consisted of 1 pair on a business app and even then we started mid way through the project.

With a few more people and a lot more GUI new challenges arose. It's hard to read everyone styles. Even my own because I keep changing my mind on what's a good test.

Sometimes the tests make me avoid changes I think are easy. The problem with that statement is if the test is complex the code probably doesn't follow the one responsibility rule. So one change may be easy, but without a refactor, each "easy" change is going to be harder and harder.

The thing to remember if you are learning is to shift thinking away from TDD as QA to TDD as design.

How your tests read is very important to introducing and getting it to stick on a team. My coworker Lee said, he often still goes to the code to figure out what something is doing and not the tests.

Readability is highly important to TDD. If your classes don't have single responsibility your test setup is going to be ugly and unreadable. Its going to be hard to maintain, and even harder to refactor without just rewriting the tests.

I first blamed the indirection and DRY applied to test code but Wendy correctly pointed out that was the result of the class under test was too complex.

Edward said...

I sat down to try to imagine what code you would have written and came up with the following. I wonder how similar this is to the real code.

---

using Rhino.Mocks;
using NUnit.Framework;

namespace TddHelloWorld
{
    public interface IConsole
    {
        void WriteLine(string text);
    }

    public class Greeter
    {
        IConsole console;

        public Greeter(IConsole console)
        {
            this.console = console;
        }

        public void GreetTheWorld()
        {
            console.WriteLine("Hello, World!");
        }
    }

    [TestFixture]
    public class GreeterFixture
    {
        MockRepository mockery;

        [SetUp]
        public void SetUp()
        {
            mockery = new MockRepository();
        }

        [Test]
        public void GreetTheWorldShouldTellTheConsoleToEmitHelloWorld()
        {
            IConsole console = mockery.CreateMock<IConsole>();

            using (mockery.Record())
            {
console.WriteLine("Hello, World!");
            }

            using (mockery.Playback())
            {
                Greeter greeting = new Greeter(console);
                greeting.GreetTheWorld();
            }
        }
    }
}

kris said...

mockery is an odd choice for the name of the MockRepository member

wunda said...

Edward,

That is very similar to what we came up with :)

Thanks for the contribution!