The internet is full of arguments about whether TDD is a good practice, a required practice, or a bad practice. When looking at these arguments, it becomes clear that there is no real shared meaning of what TDD actually is. Each blog post defines TDD in its own way and has its own claim of what TDD promises. The following is a fun exploration of what TDD means and what it doesn’t, with no claims to having a definitive answer.
That’s what it says on the tin, but what does that mean?
Sure, ok. The tests inform the direction of the design. But you still need good design skills. So what’s TDD?
Tests Drive Design!
Now you’re just repeating yourself. You need a strong grasp of good design ahead of time, to make sure the tests drive you in the right direction.
Testable Domain Decisions!
Huh? Are you saying that TDD is about declaring decisions that are made in a domain and making them testable? Umm, I guess?
Test Desideratum Declared!
Did you use a dictionary for that? No, just Kent Beck? Fine, whatever. Completely meaningless though.
Ok, now you’re just spitting out random words.
Think, Design, Develop!
Really? That’s TDD? I thought TDD was all about writing tests first, not that other stuff…
Theory Distracts Delivery
Your random words are becoming obnoxious. Does anything mean anything any more?
Time Determines Decoupling
Oh boy, I think you’re losing it!
Three Daring Disciplines!
Are you talking about those three laws? Meh!
Time, Databases, Dependencies
What?! Those are the very things that make TDD so difficult!
Terminate Deranged Digressions
That’s it! It’s obvious no one really knows what TDD is. Everyone has their own definitions. Their own insistence that it’s not TDD if you don’t do X, or Y. If you design all your tests up-front, i.e. write a schematic, does that count as TDD? Some will say yes, others will say no.
Enough silliness. Let’s ditch the labels and get to the essence of things.
First, there are the three laws of TDD.
Law 1: You are not allowed to write any production code unless it is to make a failing unit test pass.
Law 2: You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
Law 3: You are not allowed to write any more production code than is sufficient to pass the one failing unit test
– Uncle Bob Martin
So, looking at these three laws, what do we learn?
There is a fast feedback loop. We can’t write much code before one of those laws moves us onto the next one. Ok, so if there isn’t a fast feedback loop, it’s not TDD?
Well, maybe it’s TDD, but it’s not TDD that will be helpful in the long term. Eventually, the team will tire of it and move on to something with better dopamine hits and less discipline.
- Red – Write a failing test first
- Green – Get that test to pass
- Refactor – Clean up the code, fix the design flaws, make the tests more specific and the code more generic.
Right, let’s unpack those.
We need a failing test to test our test. We need to know before we start moving forward that we know what we’re looking for.
Make the tests pass: that’s easy and obvious. Guess it’s just there for sake of completeness.
Refactor? What? Why? What’s refactor doing there?
Really? It’s the most important part of the whole TDD experience? The Red and Green are just there to make the Refactoring part safe? Then why does everyone only talk about green passing tests and red failing tests? I thought refactoring was only for legacy code, i.e. code without tests!
Yeah, I don’t know why Refactoring gets the short stick. It really is the second most important aspect of TDD.
So what’s the most important part of TDD?
Before you start your journey, know where you’re headed. The most important aspect of TDD is being able to break down the goal into the next simplest and easiest step that will yield the greatest amount of knowledge. This is what’s so hard. Lack of knowledge makes tests so brittle and fragile.
How do you know if you know where you are headed? Because the next tests are easy to write and think about. If the next test isn’t easy, you aren’t headed on the right path.
Obviously, that doesn’t mean all tests are easy to write. If they were all easy, you could go in any order. But each step becomes easier as you choose the next easiest step to get you closer. As the system gets built up, as the code becomes more generic, and the tests become more specific, your gut tells you where the next easiest test will be. And this is what they mean when they say that TDD is really all about the emergent design.
TDD lets you think with your whole self, from your analytical mind to your reptilian brain. To the hard analysis, figuring out where you want to go, and reasoning about the next smallest step that takes you there. Then allow your reptilian brain to give you the gut reaction, verifying that you’re headed in the right direction. If your gut says no, engage the analytical mind and find the right step. You can’t start your journey until you know where you’re going, and you can’t write any code until you know how to test it.
Ditch the labels, ditch the ceremony, but start with a Test as you Discover the next step towards your Destination. 😉
Avi Kessner, Software Architect
Avi started as a Flash Animator in 1999, moving into programming as Action Script evolved. He’s worked as a Software Architect at OnceHub since September 2019, where he focuses on Cloud Native Automated workflows and improving team practices. In his free time, he enjoys playing games, Dungeons & Dragons, Brazilian jujitsu, and historical European sword fighting.