Test-driven development (TDD) is an essential development practice. It along with the other XP practices have significantly helped me advance as a professional developer. However, it is common when trying to sell developers on the benefits of unit testing (and the importance of writing testable code) to ignore TDD and take the position that “as long as you are writing tests it doesn’t matter whether you are writing them first or last.” I am guilty of making this claim in the past and I think it is a mistake.
I do believe that developers who have obtained a level of mastery writing high quality unit tests and designing testable code can write tests first or last and they would be indistinguishable from each other after the fact. However, even developers who have achieved this level of mastery lean towards practicing TDD, and there is good reason for this. TDD is a practice that allows developers to quickly add fully-tested, working features to the system incrementally. The system is ALWAYS in a working state. Every developer is always in a position where they are one test (60 seconds or less) away from a perfectly working system.
The reason I haven’t focused on TDD is that it remains somewhat controversial. Developers often protest trying TDD because it is a dramatic change to how they are currently writing software. I thought by avoiding the challenge of convincing developers to get out of their comfort zone and try TDD I would be able to focus on selling why unit testing and testable code is important. However, in my experience TDD is the best way to teach the skills necessary for writing high quality unit tests and designing testable code.
When feeling schedule pressure while trying to learn, developers will go back to what is comfortable. When trying to unit test under stress it is common to fall back to wrapping untestable code in tests. This results in poor quality unit tests. They tend to:
Most importantly these tests tend to be very difficult to understand. They suffer from the same code quality issues that the production code suffers from. Often developers in this situation come to the correct realization that these tests are hindering them instead of helping them. They decide that unit testing doesn’t work, and now you have lost them.
Organizations that don’t value unit testing obviously don’t make unit tests a first-class citizen. Part of the role of the coach is to show that code without unit tests is incomplete. Again, under pressure teams will abandon unit testing when done after developing production code. I’ve heard it time and time again from developers that “we ran out of time to write unit tests.” This statement bothers me for several reasons.
I’ve encountered situations where developers were checking in tests for features weeks after the feature was considered “complete.” A very effective way to combat this is to show that unit tests are so important that they should be written first. It is a technique to ensure that even under schedule pressure developers are continuing to practice writing and improving their unit tests.
…We should consider our tests as being first. We already know we should write them first; but we should also clean them first, maintain them first, think of them first, and keep them first. We should give our tests the highest priority.
— Robert Martin from his post Test First
There are several psychological benefits to practicing TDD. I believe this is why so many of us developers who have experienced the joy of TDD are reluctant to give it up and want to share it with so many developers who are afraid it will “change the way they work”.
One of the hardest things to communicate about test-driven development is the mental state it puts you in. I remember a session on the original C3 project with Ralph Beattie…Progress was steady and unhurried; because it was unhurried it seemed slow, but looking back on how much we got done, it was clear that despite the unhurried feeling progress was really fast.
— Martin Fowler from the afterword of Kent Beck’s TDD By Example
By introducing TDD you can show that instead of facing a large unknown set of features and struggling with where to begin, developers are encouraged to break a large problem down into small, achievable goals. These goals can be further broken down so that developers can focus on the larger problem, one public method of one class at a time. It forces developers to come up with a set of micro-goals.
As Martin Fowler has stated, progress is swift but feels like it moves at a comfortable pace. There are concrete indicators of forward progress as the number of passing unit tests increases. This helps developers work without stress as there isn’t a specter of murky progress towards an unclear goal hanging over them.
The other benefit that I mentioned at the beginning of this post is that the system is always in a working state. If a mistake is made, a developer can delete a test, losing at most a minute of their day. There is no other way to work that provides a faster feedback loop for developers. As a developer you are always one unit test away from a working system. Developers who work this way find themselves rarely, if ever, having to use the debugger or figuring out where in a huge amount of untested code a problem exists.
Once a unit test suite exists for a feature, developers will have confidence that they can safely refactor the code to continue to improve its design over time. If you have read Martin Fowler’s Refactoring book, several refactorings are considered “safe” because they preserve the behavior of the code. These safe refactorings are even easier now thanks to the refactoring tools for modern IDEs. However, a significant number of the refactorings from the book are not safe and require unit tests to ensure that the developer did not introduce an error.
Unit tests serve as executable documentation to demonstrate how the object should behave in a variety of scenarios. Since the unit tests are versioned with the code, they need to be kept up to date in order to pass. Therefore, they do not suffer from the problems inherent in other forms of documentation such as written specifications or code comments. Those forms of documentation quickly become outdated, thus they cannot be trusted.
TDD and pair programming come together in a practice called Ping Pong Pair Programming. I won’t talk about it in detail because I wrote a blog post about the subject that you can read about here. As a coach, pairing with developers to show them these practices is the best way to help them achieve mastery.
As the coach, write the first test and start a conversation. Show that by writing the test first it guides developers to writing well designed software. Testable code is, by its nature, decoupled.
When you follow the three rules of TDD, all your code will be testable by definition! And another word for “testable” is “decoupled”. In order to test a module in isolation, you must decouple it. So TDD forces you to decouple modules. Indeed, if you follow the three rules, you will find yourself doing much more decoupling than you may be used to. This forces you to create better, less coupled, designs.
— Robert Martin from his post The Three Rules of TDD
Use these conversations during pair programming as an opportunity to touch on all of the valuable attributes of well designed code and how all of those attributes make unit testing easier:
As you continue to pair with developers they will begin to see how much easier the code is to maintain, understand, extend, and reason about.
TDD and pair programming provides the best framework to educate developers on the value of writing testable code and how to do it properly. If you are trying to sell an organization on unit testing and writing testable code, encourage the developers to pair with you and start the conversation with the first unit test you write with them.