12/17/2020

String Calculator

This is an example how interactive process in TDD can help you think and expand on requirements.


Here are the concrete requirements:

STRING CALCULATOR

Before you start:

Try not to read ahead and do one task at a time.

Create a simple String calculator with a method int Add(string numbers).

The method can take a string representing numbers separated by commas, and will return their sum (for an empty string it will return 0) for example “” or “1” or “1,2”.

1. Start with the simplest case of an empty string and move to 1 and 2 numbers.

2. Allow the Add method to handle an unknown amount of numbers.

3. Allow the Add method to handle new lines between numbers (as well as commas).

a. The following input is ok: “1\n2,3” (will equal 6).

b. The following input is NOT ok: “1,\n”.

4. Support different single character delimiters (case-insensitive).

a. To change a delimiter, the beginning of the string will contain a separate line that looks like this: “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the delimiter is ‘;’.

5. Calling Add with a negative number will throw an exception “Negatives not allowed” - and the negative that was passed in. If there are multiple negatives, show all of them in the exception message.

6. Numbers greater than 1000 should be ignored, so adding 2 + 1001 + 13 = 15.

7. Allow multiple delimiters like this: “//[delim1][delim2]\n” for example “//*%\n1*2%3” should return 6.


Doing one task at a time suggests approaching the problem interactively - one task at a time - one test at a time and then expand on the tests.


Starting with the simplest case - returning 0 if empty string.




using System;
using Xunit;
namespace Calculator.Tests
{
public class StringCalculatorTests
{
[Fact]
public void Given_an_empty_string_should_return_zero()
{
var result = new StringCalculator().Add(string.Empty);
Assert.Equal(0, result);
}
}
}


Writing this first test, the visual studio will suggest you don't have StringCalculator class created - the SUT- system under test. So that's what we need to create first.

public class StringCalculator
{
public int Add(string numbers)
{
return -1;
}
}


This is enought code so the two projects - the test project and the class library project containing the SUT can be copiled. Well they are now, but the test fails. Lets write the code that fixes the test.

public class StringCalculator
{
public int Add(string numbers)
{
if (string.IsNullOrWhiteSpace(numbers))
{
return 0;
}
return -1;
}
}


This is quite simply returning 0 if the string is empty, which makes our first test pass.

You can have a look at https://github.com/velchev/CTCalculator where I have added commits on every step of the iterative process, just so you can see the way of thinking about the problem.

The next requirement is when we have a single number in the string to return the same number as a result

[Theory, AutoData]
public void Given_a_single_number_should_return_the_number(StringCalculator sut, int number)
{
var inputString = $"{number}";
var result = sut.Add(inputString);
Assert.Equal(number, result);
}


Here I used Autofixture for xUnit. This is just to simplify the Arrange phase of the tests and to focus more on what the test is. It supplies SUT and random positive number. I am preparing the input and asserting on the result,  very clear intent.

Again this will fail as we haven't implemented this logic yet.

using System;
namespace Calculator
{
public class StringCalculator
{
public int Add(string numbers)
{
if (string.IsNullOrWhiteSpace(numbers))
{
return 0;
}
return int.Parse(numbers);
}
}
}


The way you can think here is if is not in the test we are not writing any extra code. This basically drives the TDD - tests needs to drive the implementation and not anything else. In this way no logical path is hidden, no surprises in the way the system works and clear documentation of what SUT does.

You can go through the commits of the repository for all the further steps. It was nice to implement this pure example of TDD. Some may argue that you can even think about requirements as user requirements and demonstrate BDD here, but they are so technical, there is no user to be part of the story which translates to meaningful text.

The next time you are given story write BDD style requirement and then on the implementation of specific method write some TDD style code and you will see it allows your thinking to focus on what you need to implement and not how till you need to implement, but by the time you need to implement the failed test you will know exactly what you need to write to pass it.

I hope you enjoyed this small post. Noting unique, but is my own interpretation of an Interview Task which I did recently.