EDITING BOARD
RO
EN
×
▼ BROWSE ISSUES ▼
Issue 15

Test Driven Development and incremental design

Alexandru Bolboacă
Agile Coach and Trainer, with a focus on technical practices
@Mozaic Works



MANAGEMENT

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:

  • Writes only one automated test that fails (often called the "Red" step)
  • Does the smallest change in the code to make the test pass ("Green" step)
  • Refactors the code ("Refactor" step)

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:

  • advancing the design (the solution)
  • verifying that the changes in the code did not affect the problem solution created until the tests were run.

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:

  • solves a problem...
  • the simplest and elegant way possible

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.

  • Premature generalization (as other "cognitive biases"2). Often programmers want to obtain a generic solution, before having enough specific cases to justify it. To extreme, this tendency can create an apparently well concieved code, but which is very difficult to maintain.
  • The tendency to use known solutions. "When you have a hammer, everything you see are nails", as the old saying is. The problems the programmers solve each day can seem, at a superficial level, very similar. The reality is that in programming solutions depend a lot on small details.
  • Fast requirements change. It is already well known that the requirements change from one day to another. One solution that was good yesterday might not fit today anymore.

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:

  • Both the structure and the code are easy to understand for all the programers involved
  • Most of the required functionalities can be implemented with minimal changes in the code.

This is possible when small and very specialized classes work together by following some very well defined contracts between their interfaces.

  • It is easy to check that the changes in the code did not affect the existing implementation.

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:

  • By defining exemples, the problem becomes clearer. Even more, the examples can be discussed easier with non-technical persons (customers) than the code.
  • The list of examples is filled-in as new behaviours are identified during development. By iterating, the chances of forgeting parts of the problem decrease.
  • Simplifying the problem lets diminishing the complexity so that our brain can manage it.
  • Implementing the simplest solution for every example leads to avoiding premature generalization.
  • The solution is always simple (as much as the problem lets it to be), which makes easier to change it in the case of requirements change.

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:

  • Learning necessary techniques to write simple unit tests. The test doubles (most important stubs și mocks) are very important here.
  • Simplify. This is an ability that evolves in time, by exercise and with feedback from other people.
  • Identifying simmilarities from the code. Some simmilarities are obvious, but others are more subtle and can be seen with training.
  • Design. Simmilarities can be minimized by several ways, and some of them lead to better results than the others. The knowledge of software design (design patterns, design principles from which we mention SOLID principles and the four elements of simple design) are essential to create the simplest solutions.
  • Focusing more on the problem and less on the solution. School teaches programmers to think about solutions. Incremental design asks to identify examples and simplifying the problem before writing the first line of code. The programmer that tries TDD needs to push back the tendancy to think about design or about code at this first stage.
  • Fast and efficient refactoring. Refactoring can take a lot of time if it is not well mastered by programmers. Exercising the refactoring techniques with the purpose to increase the speed is very important when using these techniques in production.

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

Conclusion

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

VIDEO: ISSUE 109 LAUNCH EVENT

Sponsors

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects

VIDEO: ISSUE 109 LAUNCH EVENT