Archive

Archive for June, 2014

Make it run, make it right: The three implementation strategies of TDD

June 18, 2014 1 comment

I’ve been doing TDD for a while now, as well as running TDD-based code dojos. I really like it as a method of programming, and use it for the majority of the production code I write.

However, there’s one important area I’ve never got quite straight in my head, around the point at which you go from ‘making it pass quickly’ to ‘implementing it properly’. I’ve always followed the rule (picked up at some past dojo) to make the test pass in the simplest way possible, then refactor if necessary before continuing with the next test. Making it pass simply often involves returning a hard-coded value, but I’ve never fully got a handle on when that value should get replaced with the ‘real’ implementation, and related to that, when certain things should be refactored.

Following some recommendations, I finally read Kent Beck’s Test-Driven Development By Example, an influential book on TDD published back in 2002. Yay!  Finally, the last piece of the puzzle was revealed. Well OK, probably not the last, but an important one…

There are several useful guidelines in the book – for example he specifies the TDD cycle as ‘Write a test, Make it run, Make it right’ (similar to the more commonly used ‘red, green, refactor’ – not sure which came first).  The one I’m focussing on today is his strategy for the implementation of functionality, i.e. the ‘make it run’ and ‘make it right’ phases.

To take the descriptions from the book, the three options you have are:

Fake It – Return a constant and gradually replace constants with variables until you have real code

Obvious Implementation – Type in the real implementation

Triangulation – Only generalize code when we have two examples or more

This is already getting interesting – we now have three well-defined methods of proceeding.  Looking back, I think part of my problem has been trying to stick to one rule in the many and varied situations that arise when coding a solution with TDD.

Lets have a look at these in a bit more detail, with some example code.  The examples are based on the Checkout kata, which involves implementing a supermarket checkout system which calculates the price of items bought. I haven’t included every step, but hopefully you can fill in the blanks.

Fake It

One of the guiding principles behind these strategies is that you want to get a passing test as quickly as possible.  If that means writing some dodgy code, then so be it – it’s not a concern, because one of the other principles is that the code should be constantly and aggressively refactored.  So get the test passing, then you have a safety net of passing tests which enable you to refactor to your heart’s content.

Often, the quickest way to get a test passing is to return a hard-coded value:

[Test]
public void scanning_single_item_gives_single_item_price() {
    var till = new Till();
    till.Scan("Apple");
    Assert.That(till.Total, Is.EqualTo(10));
}

public class Till {
    public void Scan(string product) {}

    public int Total {
        get {
            return 10;
        }
    }
}

Once that’s done, we can immediately get to refactoring. Kent’s main goal of refactoring is to remove duplication anywhere you can spot it, which he believes generally leads to good design. So where’s the duplication here? All we’re doing is returning a constant. But look closely and you’ll see it’s the same hard-coded constant defined in the tests and the code. Duplication.

So how do we get rid of it? One way would be to have the checkout take the value from the test:

[Test]
public void scanning_single_item_gives_single_item_price() {
    var applePrice = 10;
    var till = new Till(applePrice);
    till.Scan("Apple");
    Assert.That(till.Total, Is.EqualTo(applePrice));
}

public class Till {
    private int _applePrice;
    public Till(int applePrice) {
        _applePrice = applePrice;
    }

    public void Scan(string product) { }

    public int Total {
        get {
            return _applePrice;
        }
    }
}

Well, not how I’d usually implement it, but that makes sense I guess – the checkout would be getting it’s prices from somewhere external, and passing them in like this helps keep the checkout code nice and self-contained, and adhering to the open/closed principle.  Maybe removing duplication really can help with a good design..

Of course, the checkout would need all prices passing in, not just the price for one product.  But as usual with TDD, we’re taking small steps, and only implementing what we need to based on the current set of tests.

Obvious Implementation

One of the ‘rules’ of TDD which I make people follow in dojos has been to always start with the simplest possible implementation.  This is a concept which mightily annoys some experienced devs when they first try TDD – ‘Why return a hard-coded value’, they ask, ‘when I know what the implementation’s going to be?’.

Well, they will be most pleased with the second option, which states that if the implementation is obvious and quick – just type it in!

So if for our next test we end up with

        const int applePrice = 10;
        const int bananaPrice = 15;
        Till till;

        [SetUp]
        public void setup() {
            till = new Till(new Dictionary<string, int> {
                {"Apple", applePrice},
                {"Banana", bananaPrice}
            });
        }

        [Test]
        public void scanning_different_item_gives_correct_price() {
            till.Scan("Banana");

            Assert.That(till.Total, Is.EqualTo(bananaPrice));
        }

If we’re feeling plucky, we might just go for it:

    public class Till {
        private Dictionary<string, int> _prices;
        private string _scannedItem;

        public Till(Dictionary<string, int> prices) {
            _prices = prices;
        }

        public void Scan(string product) {
            _scannedItem = product;
        }

        public int Total {
            get {
                return _prices[_scannedItem];
            }
        }
    }

However, if you have any doubts, get a failing test, or it’s just taking too long – you’ve taken the wrong option. Back out your changes, and go back to Fake It.

Triangulation

The last option is the least favoured by Mr Beck, reserved for those times when he’s ‘completely unsure of how to refactor’, and ‘the design thoughts just aren’t coming’.  For me, going by the rules I’ve learned in past dojos, it’s one I quite often end up using.  Essentially it involves using using Fake It to get a test passing, but instead of going straight to the real implementation, entering another test to force you into writing a generalised solution.

Now, we’re starting to scan multiple items:

        [Test]
        public void scanning_multiple_items_gives_sum_of_prices() {
            till.Scan("Apple");
            till.Scan("Banana");

            Assert.That(till.Total, Is.EqualTo(applePrice + bananaPrice));
        }

We have to update the code a little, but can fake the important bit:

    public class Till {
        private Dictionary<string, int> _prices;
        private List<string> _scannedItems = new List<string>();

        public Till(Dictionary<string, int> prices) {
            _prices = prices;
        }

        public void Scan(string product) {
            _scannedItems.Add(product);
        }

        public int Total {
            get {
                if (_scannedItems.Count == 2) {
                    return 25;
                }
                return _prices[_scannedItems[0]];
            }
        }
    }

Obviously, checking the number of scanned items isn’t going to work for long, but instead of refactoring immediately we can write another test which will force us to change it.

        [Test]
        public void scanning_different_multiple_items_gives_sum_of_prices() {
            till.Scan("Banana");
            till.Scan("Coconut");

            Assert.That(till.Total, Is.EqualTo(bananaPrice + coconutPrice));
        }

And now we enter the general solution:

        public int Total {
            get {
                return _scannedItems.Sum(item => _prices[item]);
            }
        }

Since using these strategies, I have also found that Triangulation isn’t needed much, as if you stick strongly to the ‘refactor away duplication’ rule, a single Fake It with immediate refactoring usually does the job.

Conclusion

It’s fair to say that I haven’t mastered TDD yet, even after so long, but I’m pretty happy with it now, thanks to the guidance like this. I still find situations where I’m unsure how to proceed, but they are getting rarer. Trying to follow these three strategies has certainly helped, and the ‘refactor to remove duplication’ concept by itself has been very useful. I hope you get some benefit from them too.

Advertisements
Categories: C#, TDD, Unit Testing