Test Driven Development is a very popular software development methodology focused (guest what?) on tests.
However, despite of being well known, there’s some criticism on this methodology from prominent professionals and thinkers.
A few days ago, there was a very interesting talk about this issue between Martin Martin Fowler (that elaborated the Dependency Injection definition), Kent Beck (creator of TDD and JUnit framework), and David Heinemeier Hansson (creator of Ruby on Rails).
Let’s review some highlights from that talk in the next topics. But let’s start with a brief review of TDD…
How TDD works
Different from the “traditional” development life-cycle composed by Specification, Implementation and Test, TDD begins testing the interfaces of the system.
At first, all tests will fail. The developer’s goal is to make they succeed. In theory it guarantee the focus on what really should be done and provides a concrete measure of progress.
In general, TDD adopts Unit Tests. Both are different things, but closely related.
Each Unit Test tests only one function or method of the system. Often they are automated.
It seems too simple, but implies that the routine under test can’t depend on other routines, otherwise the test is subjected to third part failures.
This way, in order to test routines that naturally depend on other classes and methods, resources like databases and files, and other stuff, developers need to create simulations of these elements so the routine works without them during the test.
This is the mock idea, i.e., something that mimics the original dependencies.
For instance, a method that would normally return a value from database could be replaced by a method that returns a fixed value for a particular test. Each language, framework, and tool should provide proper ways for doing that. This is also a manner to assess the quality of these tools!
Benefits of TDD
Writing tests in advance brings various advantages:
- Helps in the understanding of the requirements, since you have to understand them and also what the system should do in order to write down tests.
- Provides a clear goal in the development, that is, to make all tests to pass.
- Increases the visibility of what is or not ready, since the tests are generally more reliable indicators of progress than something “implemented, but not tested”.
- The automation enables Continuous Integration with some level of guarantee the system won’t break, since all unit tests can be executed over and over as regression tests.
As a general rule, everything has its advantages and also its negative points. We always have to take care with “evangelists” that will make some technology or methodology looks like it hasn’t problems or has zero impact.
This also applies to TDD. Let’s see some of its reservations:
Increases the effort
It’s necessary to spend much more time creating tests and mocks, not to mention the Continuous Integration infrastructure. Of course it can be considered as an investment in quality instead of a penalty, but in practice not everyone can afford the overhead.
Increases the complexity
The architecture becomes more complex since Dependency Injection and other decoupling techniques are abundantly used everywhere.
Changes in requirements are much more costly, since you need to rewrite the code of the system itself plus all the tests and mocks involved.
If the engineer don’t know well what he’s doing, the system will end with tons of kludges.
Intrusiveness in the main code
In some situations, you’ll have to code in a specific way in order the code can be tested later. The application architecture is then affected by outside unnatural factors.
In short, the design is influenced by tests, what is not desirable since is one more thing to be considered and to grab the architect’s attention.
Testing everything is almost impossible
Many technologies are hard to test independently or even in integration tests.
Think about user interfaces, for example. How many technologies allow us to run it in a decoupled way? There’s tools like Selenium for web apps, but they also brings tons of other limitations and dependencies.
Is TDD dead?
We just saw every benefit comes at a cost. This is the main point about TDD being dead. Is the cost of testing everything and creating mocks for every dependency worthy? Let’s look at some comments about the aforementioned talk.
David Hansson starts arguing that lots of people can’t do TDD because of the nature of their work. He talks about the need of numerous mocks and how it makes coding more difficult.
Kent Beck continues and tell us about situations where the team couldn’t refactor their code because the tests were so coupled to the implementation, with two or three levels of mocks, that any modification affected countless of them. He also asserted TDD fits better certain scenarios, for instance, when requirements are clear enough so you can write them in form of tests directly. On the other hand, there are situations when the developer finds out little by little what he actually should do. This is recurrent in framework development and algorithms for analyzing non-structured data.
Fowler argues the major benefit of TDD is a self-testing code. But this could be achieved in other ways. He also talks about those who say “you aren’t doing unit tests right” because of some dependencies during the test. According to him, the “purity” of the definition is not so important, but the test should work and be useful. Furthermore, there’s alternative definitions of Unit Test.
It’s important to comprehend TDD is not the silver bullet of software development. It brings various potential challenges and problems, mainly if there’s not enough experienced people in the team.
Tests are essential, but for each project we could have different kinds of tests, whose cost-benefit should be evaluated for each individual case.
Tests shouldn’t be the goal in itself, even in TDD. Their true goal is guarantee we’re delivering what the client really needs.
Time and effort invested in tests also should be pondered. It affects directly the quality, but the quality has its price. Who will pay for it?
About unit testing, we shouldn’t pursue only purity. The important is the test tests, that is, it works for that situation.
Finally, a general rule could be: use TDD moderately.
Here is the link of the Hangout with those great guys: