Test-Driven Development (TDD)

What is Test-Driven Development (TDD)?

large__8282043567Test-Driven Development is a process that relies on the repetition of very short development cycle. It is based on the test-first concept of Extreme Programming (XP) that encourages simple design with high level of confidence.

The procedure of doing TDD is following:

  1. Write a test
  2. Run all tests
  3. Write the implementation code
  4. Run all tests
  5. Refactor

This procedure is often called Red-Green-Refactor.

While writing tests we are in the red state. Since test is written before the actual implementation, it is supposed to fail. If it doesn’t, test is wrong. It describes something that already exists or it was written incorrectly. Being in green while writing tests is a sign of false positive. Tests like that should be removed or refactored.

Next comes the green state. When the implementation of the last test is finished, all tests should pass. If they don’t, implementation is wrong and should be corrected.

The idea is not to make the implementation final, but to provide just enough code for tests to pass. Once everything is green we can proceed to refactor the existing code. That means that we are making the code more optimum without introducing new features. While refactoring is in place, all tests should be passing all the time. If one of them fails, refactor broke an existing functionality. Refactoring should not include new tests.

Speed is the key

large_5836417589I tend to see TDD as a game of ping pong (or table tennis). The game is very fast. Same holds true for TDD. I tend not to spend more than a minute on either side of the table (test and implementation). Write a short test and run it (ping), write the implementation and run all tests (pong), write another test (ping), write implementation of that test (pong), refactor and confirm that all tests are passing (score), repeat. Ping, pong, ping, pong, ping, pong, score, serve again. Do not try to make the perfect code. Instead, try to keep the ball rolling until you think that the time is right to score (refactor).

It’s not about testing

T in TDD is often misunderstood. TDD is the way we approach the design. It is the way to force us to think about the implementation before writing the code. It is the way to better structure the code. That does not mean that tests resulting from using TDD are useless. Far from that. They are very useful and allow us to develop with great speed without being afraid that something will be broken. That is especially true when refactoring takes place. Being able to reorganize the code while having the confidence that no functionality is broken is a huge boost to the quality of the code.

The main objective of TDD is code design with tests as a very useful side product.

Mocking

In order for tests to run fast thus providing constant feedback, code needs to be organized in a way that methods, functions and classes can be easily mocked and stubbed. Speed of the execution will be severely affected it, for example, our tests need to communicate with the database. By mocking external dependencies we are able to increase that speed drastically. Whole unit tests suite execution should be measured in minutes if not seconds. More importantly than speed, designing the code in a way that it can be easily mocked and stubbed forces us to better structure that code by applying separation of concerns. With or without mocks, the code should be written in a way that we can, for example, easily replace one database for another. That “another” can be, for example, mocked or in-memory database.

An example of mocking in Scala can be found in the Scala Test-Driven Development (TDD): Unit Testing File Operations with Specs2 and Mockito article. If your programming language of choice is not Scala, article can still be very useful in order to see the pattern that can be applied to any language.

Watchers

Very useful tool when working in the TDD fashion are watchers. They are frameworks or tools that are executed before we start working and are watching for any change in the code. When such a change is detected, all the tests are run. In case of JavaScript, almost all build systems and task runners allow this. Gulp (my favorite) and Grunt are two out of many examples. Scala has sbt-revolver (among others). Most of other programming languages have similar tools that recompile (if needed) and run all (or affected) tests when the code changes. I always end up having my screen split into two windows. One with the code I’m working on and the other with results of tests that are being executed continually. All I have to do is pay attention that the output of those watchers corresponds with the phase I’m in (red or green).

Documentation

Another very useful side effect of TDD (and well structured tests in general) is documentation. In most cases, it is much easier to find out what the code does by looking at tests than the implementation itself. Additional benefit that other types of documentation cannot provide is that tests are never outdated. If there is any discrepancy between tests and the implementation code, tests fail. Failed tests mean inaccurate documentation.

Tests as documentation article provides a bit deeper reasoning behind the usage of tests instead of traditional documentation.

Summary

In my experience, TDD is probably the most important tool we have in our software toolbox. It takes a lot of time and patience to become proficient in TDD but once that art is mastered, productivity and quality increases drastically. The best way to both learn and practice TDD is in combination with pair programming. As in a game of ping pong that requires two participants, TDD can be in pairs where one coder writes tests and the other writes the implementation of those tests. Roles can switch after every test (as it’s often done in coding dojos).

Give it a try and don’t give up when faced with obstacles since there will be many.

Test Driven Development (TDD): Best Practices Using Java Examples is a good starting point. Even though it uses Java examples, same, if not all, practices can be applied to any programming language. For an example in Java (as in the previous case, it is easily aplicable to other languages) please take a look at TDD Example Walkthrough article.

Another great way to perfection TDD skills are code katas (there are many on this site).

What is your experience with TDD? There are as many variations as there are teams practicing it and I’d like to hear about your experience.

photo credit: pablitux via photopin cc
photo credit: Vinqui via photopin cc

8 thoughts on “Test-Driven Development (TDD)

  1. Maciej Rumianowski

    Great article! Thanks to jcg I have discovered your website.

    About the TDD I can’t say speed is the key. I see it as test and implementation are two different thought processes that help you double check logic and design. With ping pong it feels like you can loose point, because of fast action.

    Mocking is great, but Developers have to be careful not to fall into a trap of enormous amount of layers.

    Reply
    1. Viktor Farcic Post author

      Thanks for the encouragement.

      I think that speed is relative to the size of the unit/functionality one is developing. I tend to organize my methods/functions/classes in a way that they are very small, easy to understand and test/develop. In that case, I do kind of play ping pong with switches between the test and the code being usually not more than a minute. On the other hand, there are bigger units (let’s call them behaviors) that take much more time from test to development and can span multiple layers and applications. In that case I use BDD approach to define the behavior and develop it later. Time span can be anything from hours to days. While working with BDD I still do TDD. It’s something like:
      1. Define behavior in BDD format.
      2. Start developing that behavior using TDD.
      3. Confirm behavior by running BDD scenarios.

      I agree on your point regarding mocks. There is a strong tendency by some to write too many layers or too many mocks. Both are, in my opinion, a smell of bad design. Things should be as simple as possible but not simpler than that. However, it is often hard to find that balance between simplicity, quality, re-usability and test-ability.

      Reply
      1. Maciej Rumianowski

        Ok so if speed is relative than I agree, I’ve just worked with longer methods/classes recently. I also find ping pong really fast, not the best game I can play 😀

        Do you include BDD stories that are in progress in the build? Or BDD stories are an indicator of how complete is a feature?

        Reply
        1. Viktor Farcic Post author

          I tend to have two sets of BDD stories. 1st set are those that are already implemented. They serve as a regression test suite and if any of them fails, deployment is skipped. They are (together with all static analysis and unit tests) part of the continuous delivery.

          Second set of stories are those that are not yet developed and serve as an indicator of what is pending. As soon as one story is developed, it’s moved to the first group.

          Reply
  2. Pingback: Microservices Development with Scala, Spray, MongoDB, Docker and Ansible | Dinesh Ram Kali.

  3. Pingback: 10 questions et réponses de newbie* à  propos du TDD – Mes experiences de geek

  4. Pingback: Microservices for fron-end development – TechWizard

  5. Pingback: 测试驱动开发TDD(转帖) – 艾灸养生日记

Leave a Reply