loading...

TDD: The right path

jmir17 profile image Josep Mir ・5 min read

TDD: Software process based on quick iterations and continuous refactorings.

image_1: Continuous refactoring

What is TDD?
TDD stands for, as you may already know, test driven development, it is a software development process that requires to write the tests validating the code before the code itself. It is one of the core practices of Extreme Programing (XP) to have direct feedback of the state of the system.

Why should write tests?
If you came all the way until here, it is because you already realized that manually testing a piece of code is useless when try to guarantee that it works. And if at your organization, you have enough people manually testing that code to be able to guarantee something, it is not economically viable, plus, you have a stage in your lifecycle that is slowing down the whole project and it is impossible to accelerate.

In any case I am talking about removing QA teams, I am just talking about automate all the repetitive but necessary tests needed to safe release a new feature of the system. QA teams should exist, but just out of the developers work pipeline.

The fear to change the code just disappears when all the code is protected by tests. Programmers always want to improve the code, but usually they are afraid to make a change and break the whole thing. When the tests immediately tell if something has been broken, recover from that or just rollback the changes becomes a key tool to improve the quality of the code. Readability, maintainability and flexibility are attributes that come together with the tests. Also the tests document how to use your system, in code language!

What brings to the table TDD that I cannot get writing the tests after?
Just by writing the tests first, helps you to write less coupled code, and it also enforces you to design code that can be tested.

As long as the main feature of TDD is the continuous refactoring, it makes easy for the developers to increase the readability and also to learn the language because now, they have a safe playground to try more complex but also more profitable implementations. In the same way, with time, developers replace the debugger for more unit tests.

Another key feature of this practice is the incremental iterations, moving from the simplest implementation possible to the full functionality required, prevents programmers from over engineered solutions and leads to adopt simple design, another core XP practice. In the same way, the incremental iterations, reduces the code duplication.

When writing the tests after the code, the tests are adapted to the code that has been already written and not the other way around, as it should be. Quite often, it leads to biased validations for the same reason.

The two approaches

Although there are similar processes like ATDD, EDD, BDD, ... There are two main ways of doing TDD explicitly, on the one hand there is one going inside-out and on the other hand there is another going outside-in.
Spoiler alert: There is not one better than the other.

TDD Inside-out a.k.a. classicist a.k.a. Chicago school
This technique consists of starting to implement the low level modules and move from there to the high level modules. No design decisions are made upfront, the design is slowly discovered with the help of the continuous refactoring stages.

inside-out flow

In order to achieve the desired results, this technique details a workflow to become more effective. It is easy to understand, but not so easy to perform.

red-green-refactor

The technique consists of writing a failing test first, implement the code to make the test pass and then refactor that code generated, this refactorization could be to the production code or the tests. It is extremely important that we only refactor code with green(passing) tests and not in a failing stage. In refactor face, new functionality cannot be added, it is going to be through new tests that cover all the required functionalities.

This technique encourages developers to implement the code, very little by little, what it is called “baby steps”. To do so, when start writing code to pass the tests, the implementation should be like this:
  1.- Fake implementation - hardcode values -
  2.- Obvious implementation - simple statements -
  3.- Triangulation with next test - add logic to generalize the code-

TDD Outside-In a.k.a. mockist a.k.a. London school
On the other hand, this technique starts to test and implement the higher level modules and move from there to the lower level modules. This approach focuses on meeting the acceptance criteria and deliver what users really need. It needs some design upfront, despite that, it does not have to be completely detailed. We are going to iterate and adapt it.

outside-in

The outside-in technique, introduces the double loop test concept. It consists of writing a failing acceptance test for the right reasons first, and leave it there. Then, write a failing unit test for a high level module, mocking the lower level modules. Write the code, and refactor all these new code. Once the module is properly implemented, the acceptance tests should pass. It is important to keep the test independence, using mocks, between the different levels of modules.

Double loop test cycle

With this technique, the higher and intermediate modules are tested using mocks, which is good for documentation of the system and the speed of the tests. A problem is the maintainability of these tests, when the design changes, all the tests with mocks should be updated to be aligned with the new design. This may be too expensive, but once the functionality of the system is well covered by the acceptance tests, can’t we just remove some of the unit tests?


TDD learning curve
Like any other practice, it requires some time to learn it properly before using it effectively, just as any other technique in general. Or if you walk into a music store and buy a guitar or a piano, do you expect to play any song at all that very same day? Not even if you knew all the music theory beforehand. Then, what makes you think that the very first day you try TDD you are not going to struggle?

Conclusion
TDD has proved its benefits, using the practice pays off really quick. As a software engineer, you have to understand that every decision you take, it has its risks, its benefits and its inconveniences, every problem must be evaluated differently. Regarding TDD, I recommend you to learn both techniques and not stick to just one of them. You don’t have to choose one or the other to solve all the problems, use the one that fits better. And guess what, you could even combine them within the same project or not use any of them at all if that makes sense.

Posted on by:

jmir17 profile

Josep Mir

@jmir17

Software engineer as a profession, frustrated musician as a hobby, casual sportsman and social drinker in my free time.

Discussion

markdown guide
 

I don't think I've seen any conclusive evidence that proves that TDD is a more effective methodology yet. It gets a lot of praise from influential figures in the industry, but to my knowledge, there's no scientific evidence or empirical data that proves it.

And even if there is a link, could it be that devs who are apt to practice TDD tend to be more productive and efficient developers in the first place? I think whatever method helps you understand and codify the logic of your problem domain should be pursued. For me, that isn't TDD, I will generally switch from top-bottom to bottom-top design and reasoning repeatedly until I start to form a hierarchy.