The Practice of TDD
Test-Driven Development is a discipline - highlights Robert C. Martin. He compares it to the discipline of medical practitioners washing hands between procedures, or the discipline of handling a gun at the shooting range, or the rules at a martial arts dojo.
The practice of Test-Driven Development comes down to following the three laws in a loop. The laws are:
- Write NO production code except to pass a failing test.
- Write only enough of a test to demonstrate a failure.
- Write only enough production code to pass the test.
Why does it pay off? Think of debug time. Consider a team of developers working in accordance to these rules. Note that whichever developer you pick, whenever you pick them, you can be sure that within the last minute, two minutes, or even five minutes, their code executed and passed all of its tests.
Uncle Bob concludes:
What would your life be like if everything always worked - a minute or so ago? How much debugging do you think you would do?
And clarifies:
I mean - It's hard to spend an awful lot of time debugging something that worked a minute ago.
A "champion debugger" will know how to set a watch point in the debugger that will detect when the variable reaches 37 where they will be able to start debugging. "This is not a skill to be desired" - indicates Robert C. Martin. He adds: "I don't want you to spend your time debugging - I want you spending your time writing code that works".
Martin summarizes:
If I told you that you could shrink debug time by a factor of two just by following these three stupid laws - do you think it would be worth it?
Tests also work as design documents. For the consumer of a third-party library, a working set of tests is a set of working, up to date examples of how your code can be used. This is documentation for the users - documentation so formal that it executes. You can copy and paste it into your project and it will likely work at once - because it was produced by following the three laws of TDD.
Another advantage of TDD is decoupling your designs. The long-sought goal of decoupled designs becomes trivial by following the three laws of TDD.
Martin: All the benefits of TDD above combined are worth less than the courage to change you get from practising TDD.
"My tests will never be perfect!" - you may say. "They will never catch all the bugs!".
Imagine you have a codebase without a TDD test suite. With every feature you can (and often do!) introduce another slight change in behavior - another little bug. Once in a while these bugs, possibly in combination, begin to make a difference in the behavior of the system.
"Cleaning code is still risky" - you may say. Alas! By aiming to never clean the code for the fear of making bugs visible you cannot get rid of the fear. Even if you resolve to avoid any change for the fear of breaking something, you are going to have to change something as long, as you work with the program in question.
Martin says: a good suite of tests is like a parachute - you can trust it with your life, because changing a working system is a lot like jumping out of an aeroplane.
One objection to the application of TDD in daily programming is that "refactoring is rework – it would be better to write code correctly the first time".
Uncle Bob answers to that objection:
Every creative effort on the planet is done iteratively.
- Portrait painters don't paint the perfect portrait the first time,
- Songwriters don't write the perfect song the first time,
- Journalists don't write the perfect article the first time, and
- Programmers don't write perfect code the first time either.
Another objection is: "who tests the tests?". The answer to that is:
We have two streams of code: the tests, and the production code. The tests test the production code, but the production code tests the tests.
Uncle Bob discusses another objection to the practice of TDD:
– I have been told that a single change to production code can cause many hundreds of tests to break. Perhaps maintaining all those tests is simply too expensive.
– Only if they are poorly designed. Design your tests well. And if you discover better designs, refactor your tests. Treat your tests the way would treat your production code – don't allow them to become a mess.
Yet another objection:
– Test-Driven Development is a dogma. A discipline. It encourages people to follow rules instead of using their minds and thinking.
– Yes, that's true. That's what disciplines are. Disciplines are pre-made decisions. The reason we follow disciplines is so that we don't have to keep making the same decisions over and over again.
Uncle Bob continues:
– That is a strength, and a weakness. We want a certain amount of dogma because in the heat of battle we want to be able to fall back on our training and move effectively and efficiently – without constant questioning.
The Clean Coder explains:
From time to time, it's worthwhile taking a step back and reevaluating your dogmas and disciplines. You might make significant changes. You might make subtle changes. Either way, it's both appropriate, and it's necessary.
For an example, Uncle Bob refers to CPR, where you learn the discipline of CPR to be able to apply it quickly when necessary, but the guidelines get revised regularly, so what was advised at one point becomes discouraged at another.
Uncle Bob compares TDD to the 700-year-old accountant due diligence practice of double-entry bookkeeping. He notes:
Accountants do not enter all the debits first and all the credits last. They instead enter them all at the same time, keeping the balance sheet always zero. Writing your tests after you write your code is a lot like entering all the debits last – you'd never know, which entry caused the balance sheet to go out of balance.
About QA, Uncle Bob notes:
This is just basic software ethics – you don't ship code you don't know works.