TDD Rhythm Explained with Exercise

To get started with writing a unit test, it’s important to know how to write your first test.

Let’s just jump right into how to write tests (no fluff).

How to write a unit test

In this tutorial/blog series of posts, I’ll exclusively being using the Java programming language. So if you’re a JavaScript kind of guy or gal you’ll just have to bare with me.

@Test
public void testAddTwoAndThree() {
// given
    Calculator calculator = new Calculator();
    int expectedSum = 5;

// when
    int resultSum = calculator.add("2,3");

// then
    Assert.assertEquals(expectedSum,resultSum);
}

Quite simple right? Yes, it’s really basic and compared to the tests you’ll have to write at your job the principles remains the same.

How do unit tests work?

I think it’s important to understand how a unit test works before you go on your own journey writing thousands of tests during your career as a software developer.

A unit test simple verifies the expected output against the actual output of a method are equal. This verifies that the method you are calling is doing what you want it to do.

A unit test consists of three clauses: Given, When and Then clauses.

The Given clause includes classes we setup (Calculator calculator = new Calculator();) and constants (int expectedSum = 5;). Given clause constants have values that are used to compare against the return value of the method we’re calling. You don’t have to extract every constant value to a separate constant local field, but in cases where you want to improve the readability of your code you should use constant local fields so that you can easily identify what the value means (5 is the sum of 2+3).

This method in our case (calculator.add(…)) is considered the When clause. The When clause is the command or action we are trying to test against. After the work is done by our method we will use that result to verify that our expected and actual values are equal.

The Then clause is our assertion. It’s the clause that checks that our expected and actual values are equal.

That’s a unit test in a nutshell.

Introduction to the rhythm

The following sequence of steps is usually followed when writing tests.
1. Quickly add a test.
2. Make it compile.
3. Watch it fail.
4. Make it run.
5. Make it right.

Although short and concise you don’t always need to follow them. In some cases you may want to write a few test cases first and then only write the code that passes each of them, but we’ll get back to that later.

But hold on! If you ran your code right now it wouldn’t even compile (assuming you haven’t gone and implemented our Calculator class). What gives?! You could probably give it a go and try make the test pass yourself, I encourage you to do that.

I know a better and faster way to get this test to pass, first create our Calculator class that looks like this.

public class Calculator {

    public int add(String values) {
        return 5;
    }
}

Boom! Pretty simple right? We have already covered two steps in the rhythm; one being Quickly add a test and two Make it compile, although skipping Watch it fail. But we’ll shortly get back to this in the next stage.

The calculator Kata

I’m doing pretty shit at explaining tdd to you. To really step up my game and your competency we need to build something that’s real.

Now there are a ton of great things to model and drive the development of by automated tests. Chess games and bloggers to name two, but we’re going to start small and build a simple string based calculator.

This little exercise is called a Kata, and the requirements are adapted from Roy Osherove Katas TDD kata.

Our requirements

  1. Create a Calculator class with a method add(String numbers) that returns an int.
    • The method can take 0, 1 or 2 numbers, and will return their sum (for an empty string it will return 0) for example “” or “1” or “1,2”.
    • Start with the simplest test case of an empty string and move to 1 and two numbers.
    • Remember to solve things as simply as possible so that you force yourself to write tests you did not think about.
    • Remember to refactor after each passing test.
  2. Allow the Add method to handle an unknown amount of numbers.
  3. Allow the Add method to handle new lines between numbers (instead of commas).
    • The following input is ok: “1\n2,3” (will equal 6)
    • The following input is NOT ok: “1,\n”.
  4. Support different delimiters.
    • 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 default delimiter is ‘;’.
    • The first line is optional. All existing scenarios should still be supported. (Don’t break it.)
  5. Calling Add with a negative number will throw an exception “negatives not allowed” – and the negative that was passed. If there are multiple negatives, show all of them in the exception message.

Woah! That does look a bit overwhelming doesn’t it? But if you break it down a bit there are only five requirements. It’s actually quite short and sweet.

The most difficult part is actually getting started with our first test. So let’s do that now.

Requirement 1. Creating a calculator.

Let’s delete our old Calculator class and test case and start from fresh.

Now that we are following requirements, we can actually follow our TDD rhythm in a more holistic approach.

  1. Quickly add a test.
@Test
public void givenAnEmptyString_AddNothing_expectReturnOf0() {
    Calculator calculator = new Calculator();

    int resultSum = calculator.add("");

    Assert.assertEquals(0, resultSum);
}

Since you have deleted the Calculator class, it shouldn’t even compile.

  1. Make it compile.
    Okay, let’s add our calculator class.
public class Calculator {

    public int add(String values) {
        return 0;
    }
}
  1. Watch it fail.
    Run this test and watch it… pass? In this case it does pass because our return value is 0 and we’re expecting it to be 0.

  2. Make it run.
    Already running.

  3. Make it right.
    There is nothing more we can do to make it right and refactor, so leave it as is.

But we are not done yet. We still have two more test cases. One for handling an input of one number and another for handling an input for two numbers.

Now that we have some confidence in writing our first test case, we can speed things up we can have write both cases first.

@Test
public void givenAStringContainingOneNumber_AddNothing_expectReturnOfThatNumber() {
    Calculator calculator = new Calculator();

    int resultSum = calculator.add("5");

    Assert.assertEquals(5, resultSum);
}

@Test
public void givenAStringContainingTwoNumbers_AddThem_expectSumOfThoseNumbers() {
    Calculator calculator = new Calculator();

    int resultSum = calculator.add("5,2");

    Assert.assertEquals(5+2, resultSum);
}

Run our tests and watch both of them fail… Yip they do fail indeed.

To get them to pass we need to change our implementation:

public int add(String values) {
    String[] numberStringArray = values.split(",");

    if(values.isEmpty()) {
        return 0;
    }

    int sum = 0;
    for (String numberString : numberStringArray) {
        sum += Integer.parseInt(numberString);
    }
    return sum;
}

Negative test cases

There is this principle in TDD called triangulation; or in simpler terms, negative test cases.

Triangulation derives from the use of two or more radio towers to determine the location of a point in an area. TDD uses triangulation to cover the same test case but with different variation of inputs. It’s simply testing different variations of inputs on the same method.

Don’t be confused between triangulation and test cases. Yes, above we are calling the same method with different input values, but we are supporting different test cases in our requirements.

Triangulation stands out because we are testing the same test case with different input values.

Here’s an example:

@Test
public void givenAStringContainingDifferentNumbers_AddThem_expectSumOfThoseNumbers() {
    Calculator calculator = new Calculator();

    int resultSum = calculator.add("3,5");

    Assert.assertEquals(3+5, resultSum);
}

What about a case with more than two values in our expression? The requirements did say that we can only support up to two numbers.

@Test(expected = UnsupportedOperationException.class)
public void givenAStringContainingThreeNumbers_NotSupported_expectNotSupportedException() {
    Calculator calculator = new Calculator();

    calculator.add("3,5,7");
}

Our Calculator’s add method should look like this:

public int add(String values) {
    String[] numberStringArray = values.split(",");

    if(values.isEmpty()) {
        return 0;
    }

    if(numberStringArray.length > 2) {
        throw new UnsupportedOperationException("Calculator cannot add more than two numbers!");
    }

    int sum = 0;
    for (String numberString : numberStringArray) {
        sum += Integer.parseInt(numberString);
    }
    return sum;
}

Adrian van den Houten

I'm Adrian van Houten, founder of ScholarCoder and a passionate software developer for full-stack web development. Read more about me here.