As we were saying in the last articles about software craftsmanship, Test Driven Development is one of the practices considered to be at the core of a software craftsman. Besides the increasing numer of articles, blogs, short movies or books on this subject, Test Driven Development (TDD) continues to be a subject that generates confusion in the programmer communities. This article will try to structure and
to clarify the subject and to offer support to those willing to learn more about it.
The classical description of TDD is that the programmer:
This cycle repeats with a high frequency, reaching to maximum 5 minutes for experienced practitioners. For beginners, 15-30 min is the normal duration, that decreases once experience is gathered. We do not talk here about writing tests on existing code; in this case the time needed to write tests is proportional with the code complexity.
This description is very easy to show, but unfortunately it does not contain essential details for those that want to start applying TDD, and this creates confusion.
The first thing we need to understand about TDD is that, contrary to its name (development lead by tests), it is not a method of testing. TDD is a method to obtain an appropriate design for a solved problem. The tests are used with two purposes:
Because the notion of software design is ambiguous, it worths explaining what is design in this context.
Design is not anything else but "creating artifacts that solve problems"1. For programming, the artifact is the code. More exactly, at the bottom level, the created artifacts are the variables, methods, classes
and the collaboration between objects (called as well "contracts").
Two things are important for the design of a software application:
If programmers write elegant code and do not solve a problem, then by definition they did not obtain a design. The fastest and most elegat way of demonstrating that the problem is solved is by running some automated tests what can be reviewed that they cover all the aspects of the problem.
Finding some simple and elegant solutions hits a couple of obstacles:
• Incomplete understanding of the problem. The human brain has a limited capacity, and the problems programmers need to solve increase in complexity. So nobody should be amazed that sometimes not even the best programmer cannot understand all the aspects of a problem.
The realities from the day to day life of a programmer lead to the need to have an easy to change design. The requirements change, teams change, programmers learn more each day about the product and about technologies. During these days, a good design is almost synonim with an easy to change design. This is why the qualities of a good design are:
This is possible when small and very specialized classes work together by following some very well defined contracts between their interfaces.
A solution for these problems is incremental design. Incremental design means creating the design while writing the code. Incremental design is an alternative to the classic way of doing design: before starting writing code, on paper or by using special tools.
To do incremental design one needs to follow the steps:
• Analyse the problem and divide it in smaller problems. For example, in the case of creating a Tetris game, you can start with the simplest game possible: one piece of the size of a square that falls intro a well with height 1 and the game ends (alternatively, one can consider that the square filled a row that needs to be elliminated, just that the rule of elliminating the row can be easily added later).
• Identify concrete examples (expected values of input and outpu). For example, after the game begins, the piece that appears on a board into a certain position.
• Implement one example in the simplest way possible.
Incremental design fights the problems mentioned earlier in the following way:
Test Driven Development is the best known method to do incremental design. The tests code examples of usage of the solution and can be used to continuously verify the solution. Implementing the simplest solution at every moment helps premature generalization. Refactoring leads to simplifying the solution.
Because developing using TDD starts from examples, and the code written at every step is as si as possible and without generalizatins, the refactoring step implies especially identifying and reducing simmilarities from the code (also named "duplication", becaue two chunks of code do the same thing in different ways). The simmilarities can be elliminated just by generalization, which is tranlated in code by abstractions. (No, it is not about abstract classes, but about classes that serve for a larger purpose that they were initially made for).
By introducing abstractions, the programmers obtains a flexible design, perfectly adapted to the current problem. Because of using automated tests, the programmer can anytime show that his solution is perfeclty valid for the list of behaviours defined by tests. Sounds excellent, right?
But apoting TDD is not simple. At a personal level some obstacles need to be overcome:
At the level of one team, even more, a period of defining a common design and tests style is needed. For more complex environments (a lot of existing code without automated tests, more teams working at the same product, remote working, etc), the adoption needs to be treated with great care in order to avoid problems related to productivity. The recommandation is that for complex situation technical coaching is needed to manage the changes
Incremental design means creating the design while the code is written Incremental design is an alternative to the design made on paper or in specialized tools before writing code. Incremental design