Este post también está disponible en español

Introduction

Test Driven Development is a software design technique that suggests we start our code with a test and build the design from it. Some of it’s main promoters are Kent Beck, Robert Martin, Sandro Mancuso, etc. Despite the subtle differences between them, they all agree on the same guidelines.

Some of it’s advantages are a better coverage of automated tests, a better software design and fewer bugs, among others. Next, some characteristics of the classical idea by way of introduction will be detailed.

Steps: Red, Green and Refactor

First of all, we should identify the smallest testable step possible. In our example, we want to develop a calculator and it’s first functionality will be making a sum. The classic technique consists of three simple steps that we will explain next:

Red

The first step we are going to do is write a test that fails. Of course, at this stage our code probably won’t even compile. This is TDD’s most important contribution to design, using a code that doesn’t exist yet, we do it by designing it the way we’d like to use it. For more complex cases it’s important, in this step, to have a preliminary reflection on the design that we are going to use, not in detail, but a general idea.

As soon as our code compiles, we will be able to execute our test. That’s when we are going to be waiting for the test to fail for the right reason. For example, if we are testing a sum, the test returns the wrong result of this sum. An incorrect reason in that case could be a type error. Let’s see an example of how we would have the code at this stage.

Let’s see an example of how the code should look like at this stage:

@Test
fun add() {
  val calculator = Calculator()
  assertThat(calculator.add(1, 2)).isEqualTo(3)
}
class Calculator {
  fun add(num1: Int, num2: Int): Int {
    return 0
  }
}

Green

Once the code has been created from our design, we move on to the second step, which is passing the test in the simplest way possible, without worrying about the design, performance or anything else. At this moment, after seeing the test fail and now seeing it in green, we will have the certainty that our test is protecting us well from that functionality. In fact, this is fundamentally why we want to see it fail.

At this stage, our code looks like this:

class Calculator {
  fun add(num1: Int, num2: Int): Int {
    return num1 + num2
  }
}

Refactor

Finally, after these two steps, we reach the stage of cleaning our code. Starting with the productive code then following our tests, this is the moment to think of good names for our variables, classes and functions, create new objects, eliminate code duplications, etc. The interesting thing about this step is that with each small change, we can run the tests and progress with the refactor, having the peace of mind that everything continues to work well. In case something goes wrong, we know exactly where the error is and it will take no time to go back and correct it.

In our example we don’t have anything useful to improve because it is a very simple use case. Anyway, I would like to emphasize that it is at this moment and if we are applying the technique well, we should stop. This is because it is here where we can apply design, patterns, refactor techniques and more advanced things that I hope we can discuss in this space later. But as we improve our muscles by applying TDD, creating the Test and passing it, it will be something almost automatic or relatively simple in most cases.

A humble tip for this part: it is very important, during the refactor process, not to stay in the red for too long. What does it mean? It will depend on your experience. This is because we could lose one of the great advantages of using TDD, which is, in the event of an error, not being too far from the change in the code that caused it to fail in order to correct it quickly. For this reason, it is advisable to advance in the refactor through what we call baby steps, something that we can delve into later, but basically it is about taking the smallest possible step consecutively.

Conclusion

TDD brings many advantages to the development experience, but also to the quality of the final product. In a very simple and effective way, it increases the satisfaction of the development team and also the client. This is a small introduction to this world, but following the next steps, I recommend continuing to delve into refactor techniques, TDD schools and other Extreme Programming techniques to complement. We will discuss all these topics in the successive entries of this blog.