This post was edited by Taavi Kivisik
A reader recently sent me this question:
I'm getting quite serious with programming. I finished reading Clean Code and am doing a course about design patterns, nerding out on Node.js, etc. I have to say it's quite amazing to realize the level of sophistication and effort involved to make quality software. It makes me really appreciative working with engineers who are capable of working on that level.
I decided to write this email because I find it hard to find a good book to read about TDD. Coming from a traumatic experience working with a bunch of crappy software at a previous company, I’m actually quite obsessed with software quality at my new job. And guess what? I’m in an amusing situation where two code bases that my team maintain have no unit tests!
Do you have some advice on which books to read? I did a bit of research, but none of it seems convincing enough. Test Driven Development by Example by Kent Beck has the best rating on Amazon.
I’d appreciate your advice.
- Robert (not his real name)
I like this question, because it’s a topic very near to my heart.
To re-cap, Robert has read Bob Martin’s book, Clean Code, which provides excellent advice on writing good code from scratch, but Robert is facing a bunch of legacy code that doesn’t follow these practices. He has also identified that Test-Driven Development (TDD) might help. But where to start?
As strange as it may sound, I think Robert is in an ideal situation, in many ways. In my experience, the best time to learn about TDD is when you have an existing, messy code base, which doesn’t have tests. This is because one of the biggest strengths of TDD is mitigating this very situation. But until you’ve felt that pain first-hand, it’s often hard to find the motivation to take on TDD.
But what is Test-Driven Development?
Wikipedia says “Test-Driven Development is a software development process that relies on the repetition of a very short development cycle.” In Test-Driven Development by Example, Kent Beck describes TDD as a three-step process:
- Write a failing test.
- Write only enough code to make the test pass.
- Refactor to make it clean.
Further, as Bob Martin emphasizes in his post The Three Rules of TDD, it is essential to keep each step as short as possible. He writes, “the time between running tests is on the order of seconds, or minutes. Even 10 minutes is too long.”
This kills a common misconception that Test-Driven Development means writing all your test cases first, then writing your code. Doing so would just be another draconian form of the much-dreaded waterfall methodology.
The wrong starting point
I became interested in trying TDD myself after an introductory talk at a PostgreSQL conference in 2009. The fascination quickly turned into contempt as I only saw it working for artificially simple problems like a Fibonacci calculator, while adding unnecessary complexity to the process. My experience was further confirmed when I heard many of my peers also complain about the same things. This led me to conclude that iterating through the test-code-refactor steps from the very start is not the way to go. But over the next few years, my opinion slowly changed.
Start with legacy code
The first step toward change was reading Working Effectively with Legacy Code by Michael Feathers. In the preface of the book, Feathers provides a concice working definition of “legacy code”:
To me, _legacy code_ is simply code without tests.
Tests, as Feathers explains, provide a safety net when making changes. Code without tests is fragile and inflexible. Even code with the best architecture is fragile if you can’t change it with confidence that you won’t break something inadvertently. Tests provide that confidence.
This had a profound impact on my work, almost immediately. I began applying the principles and techniques described in the book to the “legacy” code I had been developing up to that point. Slowly, file by file, function by function, our test coverage grew, and our code was slowly refactored into something much more easily testable.
This was my first step in becoming proficient with TDD. Although I still thought TDD was silly, I came to appreciate, and even rely on, good unit tests. Albeit, the tests were written after the code. Then, the more time I spent refactoring legacy code to work under test, the better my code architecture became.
Even if you’re already convinced that TDD is the way to go, this book is a must-read if you must work with untested code. It will teach you many techniques for taming that legacy code, decoupling dependencies, and safely making changes, by first introducing tests to the code you’re about to change. The approach provided by this book has a nice side effect of only putting effort into writing tests for code you are about to change, as opposed to going on a large unit-testing rampage for all the code, and stopping active development.
Stop adding legacy code
Say we agree with Feathers’ definition of “legacy code”. Then, to avoid the growth of legacy code, we must implement a policy that no new code should be committed without accompanying tests. This was indeed the approach taken on my project at the time. No pull requests were approved without tests.
Early on, this meant a lot of back-and-forth with teammates, reminding each other to add tests. Sometimes someone (myself included), would feel that a particular case was an exception, and didn’t need a test, and someone else would push back with reminders about the policy.
At this stage, the biggest challenges are to get your teammates to play along, convincing them that tests are worth the effort and that they should be written now, not at some future date. Eventually, it became second nature to write code, then write tests, then create a pull request.
I was fortunate enough to work in a small team during this stage, with only three of us. We all came to recognize the value of tests pretty quickly, so the challenge in convincing them was minimal. In retrospect, convincing myself proved the hardest.
But if this is a challenge in your team, I recommend the book Driving Technical Change: Why People on Your Team Don’t Act on Good Ideas, and How to Convince Them They Should by Terrence Ryan. It’s not about TDD, per se. It’s more about team dynamics and personality types. But if you need to convince your team about TDD, or for that matter, to adopt microservices, cloud computing, or your favorite programming language, the book can help.
Adopting Test-Driven Development
Over time, there were occasions where writing a test before the code made sense. Then the number of such occasions continued to increase for me. Eventually I decided I should write tests first all the time. Although I strongly believe it’s worth it, it takes time to make it into a habit. By using TDD, I guarantee never to create new legacy code. Legacy code literally never exists, as the test is created before the code!
If you’re already accustomed to writing tests for legacy code, you probably need minimal guidance, if any, writing tests for new code. But if you do need some guidance, the go-to book for learning TDD would be the one mentioned in Robert’s question: Test-Driven Development by Example by Kent Beck. It’s a fairly short book, and an easy read, and it goes through (mostly) realistic examples of writing and refactoring code with TDD. The author takes a charitable tone, even breaking some of his own rules when he has a reason to. This is important as TDD is a tool, not a religion. Use it to boost productivity, not to beat yourself, or others, with a list of rules to be followed!
I think learning to use a specific testing framework, such as JUnit, can easily be achieved by reading the online documentation together with some trial-and-error. Although there are many books on these topics, I’ve never read them, so cannot make a recommendation.
For other general information and discussion about TDD, I would recommend googling or searching from YouTube any videos by the following giants in the field:
- Bob C. Martin (aka “Uncle Bob”), is a strong TDD advocate, and always an interesting speaker. Look for him on YouTube.
- Kent Beck is often called the “inventor of TDD”. He’s the author of Test-Driven Development, recommended above, as well as Extreme Programming Explained.
- Martin Fowler is a speaker and proficient blogger who also talks frequently about tests.
I love getting questions like this from readers. If you have a question you think I might be able to answer, please let me know! Leave a comment below, contact me on Twitter, or send an email (use the contact link at the top of the page).