This is my second attempt at Roy Osherove’s String Calculator kata. You can read the details at the original site, but the overall gist is a calculator that has an Add()
method. This method takes a string input, adds the numbers in the string together, then returns the result.
My first attempt at the problem was using a traditional TDD approach. For this attempt I’ll be using a variation of Behavioural Driven Development (BDD) as described to me by JP Boodhoo at NBDN. We’ll pick up from the point where I have created a new solution containing a single C# project called CalculatorKata.
As this is likely to be quite different to what you’ve seen before (at least, it was for me), I’ll take a bit of time to describe the details of how it works, so this instalment will be longer than the last.
Test infrastructure
To support writing tests in the style I want I’ll create a little bit of test infrastructure:
[TestFixture] public abstract class ConcernFor<T> { protected T sut; [SetUp] public void SetUp() { Context(); sut = CreateSubjectUnderTest(); Because(); } protected virtual void Context() {} protected abstract T CreateSubjectUnderTest(); protected virtual void Because() {} }
This will be a base class for our tests, or specifications if you prefer. Before each test runs our unit testing framework will setup a context, create the subject under test (SUT), then execute the Because()
method. This means we’ll create our dependencies in Context()
, create a subject under test that uses those depedencies, then call the sut
in the Because()
method. Our test itself will then assert that the right thing happened.
This is a fairly clumsy and simplistic (although reasonably understandable) version of the approach used in JP’s developwithpassion.bdd library.
Designing the Add()
method
Let’s start top-down. How’s our Calculator.Add()
method going to work? We’ll start writing a specification in CalculatorSpecs.cs
:
public class CalculatorSpecs { public class When_given_a_string_to_add : ConcernFor<Calculator> { [Test] public void Should_return_the_result_of_adding_together_all_numbers_in_the_string() { Assert.That(result, Is.EqualTo(sum)); } } }
At an abstract level this states what I want to happen. When the calculator is given a string to add it should return the sum of all the numbers in that string. The implementation of that assertion is that the result
we get is equal to the correct sum
. We’ll setup the specific instances of these variables later.
Why does this scenario occur? Because the Add()
method was called with a given expression
. Let’s add that to our specification.
protected override void Because() { result = sut.Add(expression); }
Building up a context for this to work
How are we going to get from a string expression
to our sum
integer? We’ve written the assertion and the action triggering off this scenario (the Because()
), so now we’ve got to design a context that allows this to happen. Our test title gives us a bit of a hint as to what we need to do: we need to add together all the numbers in the string. Which implies we need to get the numbers out of the string, then sum them. Two responsibilities. So let’s push each responsibility out into another object so we don’t have to worry about it yet.
First, let’s get something that will get some numbers out of our expression:
INumberParser numberParser; /* ... snip ... */ protected override void Context() { numberParser = MockRepository.GenerateStub<INumberParser>(); numberParser.Stub(x => x.GetNumbers(expression)).Return(numbers); }
This has required our design to adopt an INumberParser
with a GetNumbers()
method on it. It is going to return some numbers from an expression. What expression? What numbers? Let’s make some up:
INumberParser numberParser; /* ... snip ... */ protected override void Context() { numberParser = MockRepository.GenerateStub<INumberParser>(); expression = "(some numbers)"; var numbers = new[] {1, 2, 3, 4}; numberParser.Stub(x => x.GetNumbers(expression)).Return(numbers); }
"1,2,3,4"
so it matches the content of our numbers
variable. Comes down to personal preference, but I prefer not to be prejudging exactly how our GetNumbers
method is going to work. By putting in garbage then anyone reading this test will know that they have to look at the INumberParser
implementation to see exactly how the numbers are getting parsed. Otherwise the parsing requirements could change and then this test would be giving misleading information.Then we’ll need something to add these numbers, right?
INumberParser numberParser; IAdder adder; /* ... snip ... */ protected override void Context() { numberParser = MockRepository.GenerateStub<INumberParser>(); adder = MockRepository.GenerateStub<IAdder>(); expression = "(some numbers)"; var numbers = new[] {1, 2, 3, 4}; numberParser.Stub(x => x.GetNumbers(expression)).Return(numbers); adder.Stub(x => x.Add(numbers)).Return(sum); }
This has given us an IAdder
interface with an Add()
method on it that can sum numbers, as well as a variable name that sounds like a type of snake. When our adder is told to add the numbers we want to return a sum. It doesn’t really matter what sum, we’re just going to assume that it does its job properly. We haven’t initialised our sum
variable yet, so let’s put in any old thing.
protected override void Context() { numberParser = MockRepository.GenerateStub<INumberParser>(); adder = MockRepository.GenerateStub<IAdder>(); expression = "(some numbers)"; var numbers = new[] {1, 2, 3, 4}; sum = 42; numberParser.Stub(x => x.GetNumbers(expression)).Return(numbers); adder.Stub(x => x.Add(numbers)).Return(sum); }
All that’s left now is to create our subject under test.
protected override Calculator CreateSubjectUnderTest() { return new Calculator(numberParser, adder); }
Huh?
So what are we doing here? First, we’ve stated what our class does: when given a string it should add together all the numbers in that string. If we drill down into our context we can see how our class will accomplish this monumental feat. It will use a INumberParser
to get numbers from our expression
, and then use a IAdder
to add those numbers.
So what is Calculator
’s single responsibility? It is just coordinating its two dependencies. This level of abstraction has just flowed fairly naturally from this way of thinking about and decomposing the problem while writing our test. If you’re anything like me this will all look a little foreign, but I’ve found this style of TDD very addictive.
Those of you that have done mocking before may notice my favourite little side-effect of writing contexts like this: we have references to all the little pieces of data flowing through our class under test. We won’t need to resort to any fancy Arg<int>.Matches(...)
stuff, we just tell or mock/stub/substitute/test double exactly what to expect and what to return.
In case you are worried that this seems like a lot of work, it took me about 2 minutes to get this out.
Passing our specification
To pass this we switch to Calculator.cs
. Our implementation will mirror the context we set up while writing the test:
public class Calculator { private readonly INumberParser _numberParser; private readonly IAdder _adder; public Calculator(INumberParser numberParser, IAdder adder) { _numberParser = numberParser; _adder = adder; } public int Add(string expression) { var numbers = _numberParser.GetNumbers(expression); return _adder.Add(numbers); } }
This passes, and, as I can’t see any refactoring to do, I think we’re done. Because our class only has a single responsibility (coordinating a couple of dependencies), there aren’t a whole lot of scenarios to set up, nor a whole lot of tests to write.
Driving out our IAdder
implementation
We now have to continuing driving out the bits and pieces that the calculator needs to work. We have an INumberParser
and IAdder
that need implementations. IAdder
sounds pretty straight forward, so let’s knock that out of the way.
//AdderSpecs.cs public class AdderSpecs { public class When_adding_numbers : ConcernFor<Adder> { private int result; private int sum; private IEnumerable<int> numbers; [Test] public void Should_return_the_sum_of_the_numbers() { Assert.That(result, Is.EqualTo(sum)); } protected override void Because() { result = sut.Add(numbers); } protected override void Context() { numbers = new[] {1, 2, 3, 4}; sum = 10; } protected override Adder CreateSubjectUnderTest() { return new Adder(); } } } //Adder.cs public class Adder : IAdder { public int Add(IEnumerable<int> numbers) { return numbers.Sum(); } }
Creating a number parser
Finally, we need to tackle the INumberParser
implementation. This one was driven out a requirement at a time, similar to the approach taken for my first attempt at the problem. First I added handling for the empty string case, then a string containing a single number, then multiple numbers separated by commas etc.
public class NumberParserSpecs { public abstract class Concern : ConcernFor<NumberParser> { protected string numberString; protected IEnumerable<int> result; protected override void Because() { result = sut.GetNumbers(numberString); } protected override NumberParser CreateSubjectUnderTest() { return new NumberParser(); } } public class When_parsing_an_empty_string : Concern { [Test] public void Should_return_no_numbers() { Assert.That(result, Is.Empty); } protected override void Context() { numberString = ""; } } public class When_parsing_a_string_containing_a_single_number : Concern { [Test] public void Should_return_the_number() { Assert.That(result, Is.EquivalentTo(new[] {1})); } protected override void Context() { numberString = "1"; } } public class When_parsing_a_string_with_multiple_numbers_separated_by_commas : Concern { [Test] public void Should_return_all_the_numbers() { Assert.That(result, Is.EquivalentTo(new[] {1,2,3,4,5})); } protected override void Context() { numberString = "1,2,3,4,5"; } } public class When_parsing_a_string_with_multiple_numbers_separated_by_newlines : Concern { [Test] public void Should_return_all_the_numbers() { Assert.That(result, Is.EquivalentTo(new[] { 1, 2, 3, 4, 5 })); } protected override void Context() { numberString = "1\n2\n3\n4\n5"; } } }
The main difference between this and the previous approach is that we are only testing the number parsing here, in isolation of the addition. This should hopefully give us more flexibility in changing the parsing later.
public class NumberParser : INumberParser { private static readonly char[] Delimiters = new[] {',', '\n'}; public IEnumerable<int> GetNumbers(string expression) { if (expression == "") return new int[0]; return expression.Split(Delimiters).Select(x => int.Parse(x)); } }
We still have two responsibilities here: splitting the expression and converting each part into an integer. That’s a potential refactoring for us, but I’m happy enough with this for now.
What’s left?
The remaining feature we have to implement is allowing a custom delimiter to be specified by beginning the expression with “//”. This caused me a bit of trouble in the first instalment, so we’ll tackle this in the next part of this series so we can look at it in a bit more depth.