Not long ago, UncleBob promoted the conference talk “DevTernity 2017: Ian Cooper – TDD, Where Did It All Go Wrong“.
I’ve watched the talk, took some notes and here’s my distillation of it.
It seems like the development community lost track of what TDD is at its core. It seems we tend to opinionate a lot about concepts, patterns, methodologies, but we only know them by what we heard other people say about it… And like in a game of Chinese whispers, the original idea ends up distorted. The problem is that too many of us in the community tend to not read the books that originally published the idea.
Therefore, Ian Cooper advises us to read the original book that published the idea of TDD: “Test Driven Development: By Example” by Kent Beck, published back in 2002.
Despite risking being part of the game of Chinese whispers, Ian Cooper tells us about all the mistakes he made regarding TDD, since he started doing it back in 2004, and tells us how he should actually have done it according to Kent Becks’ book.
The gist of the problems that Ian Cooper sees with TDD is that many developers, even very experienced ones, don’t really want to do it. This is because:
- tests are slow to write;
- they “need” to test every little thing;
- you “need” to write tests for every piece of code;
- when we change the implementation we also need to change the tests;
- acceptance tests (which simulate a user clicking on a screen), are slow and too unstable because they need to change whenever the UI changes;
- they are slow to run;
- in sum, tests are annoying because they make us slow and none of us wants to be slow, we want to be the guy that quickly delivers reliable functionality!
I would also add that these tests end up only making sure that changing a piece of code doesn’t break another unrelated piece of code, although that is not the goal of the tests, that is something the codebase should do on its own by means of module isolation.
However, this is not how it should be! This is not how Kent Beck envisioned it, and Ian Cooper reminds us of that. Heres my distillation of Ian Coopers’ bits of advice:
- Refactoring is the process of changing an implementation while maintaining the same behaviour;
- One of the promises of unit tests is that, when refactoring an implementation, we can use unit tests to make sure the implementation still yields the expected results;
- Focusing on testing methods or classes creates tests that are hard to maintain because we will need to update them when we refactor the implementation of the behaviour. Furthermore, they don’t test the behaviour we want to preserve, otherwise, we wouldn’t need to update them when we refactor the implementation;
- Avoid testing implementation details, test behaviours;
- The trigger to add a new test is not the creation of a new method, nor the creation of a new class. The trigger is implementing a new requirement;
- The Unit under test is not a class nor a method, it is a module. A class may be the entire module, or a class may be the module facade, but many classes are just implementation details of a module;
- Test the stable public API of a module;
- Write tests to cover the use cases or stories;
- Do not test using Acceptance TDD, use developer tests using the implementation language, they are faster and more stable;
- Use the “Given When Then” model;
- The unit of isolation is not the class under test, but the tests themselves. Tests need to run isolated from each other, but they can and should test several classes working together if that is what is needed to test the behaviour.
- Avoid mocks at all costs, use them only to isolate the tests on the module boundaries;
- We avoid the file system and the database, to help isolate tests and make tests faster. Those are, most of the time, module boundaries.
In the end, the message is:
Test module behaviours, not implementations!