In this series I want to share my approach to TDD. I will do a simple TDD kata, sharing with you all my decisions and thoughts. It’s called commit by commit beacuse you will find here links to github with each step. There will be a commentary to each one of these. I encourage you to comment and show me the flaws in my “style” of TDD coding. I also want to learn from you. I do know that my approach has its problems and there is allways a way to do something better.

String Calculator Kata First Kata is quite simple. You can find all the details on the

Roy Oshevore’s site. Just to be brief, I am going to implement string calculator that parses values from the string and sums all of them. For example for an input of “1,2,3” the end result will be “6”. This simple kata is a good problem, to present you the way I do approach the coding with TDD. If you don’t know the concept of “Coding Kata”, no worries, have fun reading this blog post by Dave Thomas the guy who coined the term.

Step I - Empty Project

Let’s start simple with an empty project containing one test class. Yes test class with a default name Class1. Why Class1 ? Right now, I don’t want to decide on the naming of the class under test. Also just to start, first lines of code are going to be inside the Test Method. I am not creating class or method, just a code inside the Test Method. Thats one of the approaches, I’ve learned recently. Start as soon as possible with anything and check all the assumptions about the problem you are trying to solve. Don’t waste time over-thinking stuff not related to the problem at hand. I know that as engineers we do have a tendency to be mad about code quality from the start. But let’s forget about it for a while and this time do it differently ;) Approach like this is ok for not so overcomplicated and self-contained problems. With more complex stuf, I tend to think about design for a while before I write the code.

You can read more about classic and london school approach to TDD here. Here is a link to the commit with my first step. I will post links like this at the end of the steps.

Step II - The most obvious string

First requirement : for input string with numbers separated by ‘,’ return sum of these numbers. I approached TDD previously with tendency to start with tests for edge cases, null checks, exceptions. It is a very tempting approach. You can allways write something fast and make some easy “red/green/refactor happy” points. No, no, no. Start with a solution to The most basic requirement, that way you can check the requirements and the context of the problem before even touching edge cases.

Simple solution to the first requirement.

[Test]
public void ICanParseString_AndSumUpValues()
{
   var input = "1,2,3,4,5";
   var expectedSum = 15;

   var values = input.Split(',');

   var actualSum = values.Select(x => int.Parse(x)).Sum();

   Assert.That(actualSum, Is.EqualTo(expectedSum));
}

The code uses Split method on string to get all the values. Values are then parsed and summed up. Beacuse, at this moment I don’t care about the edge cases, the “Parse” method is used instead of “TryParse”. This is just a start, a first step that will influence next steps. Right now I want to start as simple as possible and prove that solution like this works and I do have a meaningfull result.

Step III - Introduce Class and Method

With a simple solution that fullfills the first simple case. I can now create a class with a method.

Commit

public class StringCalculator
{
    public object SumFromString(string input)
    {
       var values = input.Split(',');
       return values.Select(x => int.Parse(x)).Sum();
    }
}

This is a start. With class in place I can think of some simple edge cases.

Step IV - Edge Cases

The time has come for some edge cases. I can define at least two of these now: - What happens if input is empty or null ? - What to do when input is not formatted correctly ? How to check it ? Lets try to do the first one with empty, null, whitespace input scenario. What to do ? I can treat it as a wrong input throwing an exception ? or just return a zero value hiding the problem. Answer to this hard question is hidden inside the context around your problem. I have to ask a question, How is this code going to be used ? Who is going to call it ?. To keep things simple, I will stick with the return 0 option.

Commit

[Test]
        public void IfInputNullEmptyWhiteSpace_Return_0()
        {
            var input = string.Empty;
            var expectedSum = 0;

            var actualSum = new StringCalculator().SumFromString(input);

            Assert.That(actualSum, Is.EqualTo(expectedSum));
        }

These is the test. Right now it is failing, to make it green I just added simple if clause. Due to simplicity no code in here

just commit link.

Step V - Next edge case

What to do when input is not formatted correctly ? How to check it ? I am using split, in this simple form, the code does expect string in a correct format. I am assuming that if I can’t parse the string with the split function, then the input string is not correctly formated.

Commit

[Test]
public void IfInputNotFormatted_throws()
{
    var input = "1,2,3,4,,,5";

    Assert.Throws<InputStringNotFormatedProperly>(
         () => new StringCalculator().SumFromString(input));
}

As you can see the test is expecting custom exception. To be honest. I don’t know if the CustomException is needed here, maybe it would be better to just have a FormatException throwed here.

Commit

try
{
     var values = input.Split(',');
     return values.Select(x => int.Parse(x)).Sum();
}
catch (FormatException ex)
{
      throw new InputStringNotFormatedProperly(
         string.Format("Unexpected format input - {0}", input), ex);
}

Things To note

  • all of the code is in one file, there is no need to separate anything, at least right now
  • my file still has a name of Class1.cs, I don't want to rename it in this early stage. Of course, I have to make sure that this mess won't leak into codebase later.

Summary

  • don't create class and methods on the start, write stuff inside the test method
  • start simple with code fulfilling the most basic requirmenet, don't focus on edge cases