Throughout time, there have been certain questions that will always result in great battles. In one recent throw down, I drew my line in the sand and bravely asserted, “Hell no, a hotdog is not a sandwich!”
There are other more dangerous questions that we’ve all heard, of course… is Mac better than PC? Is Android better than iPhone? Are dogs better than cats? That last question is the silliest of all as the correct answer is so very obvious. Regardless, these intriguing questions have often led to disastrous consequences such as sulking and hurt feelings.
Allow me to add another one to the list: Is Test-Driven Development (TDD) a good practice?
I know, provocative. In this blog, I will discuss test-driven development, why many in our field seem to hate it, and why you should choose to still implement some of its main concepts in your development….
Test-Driven Development, An Experiment
If you are in a position of power and feel like having some fun, command that all development henceforth will be TDD. Note the blank stares, mouths falling open, and despondency washing over the developers as they bring up their LinkedIn profiles. The next morning, maybe tell everyone you were just kidding.
Just this week, I had a discussion about TDD with some colleagues. Below are some of their comments (fictional names have been used to protect identities).
“At the one place where tests were really pushed hard, there were close to 20k tests. It was extremely difficult to maintain and caused enormous pain.”
“But companies that haven’t tried it, along with ivory tower people that never have to try to maintain it, keep pushing it.”
“We had a project with 4,000 tests that were TDD-written. It was awful. The TDD style did NOT help the architecture ONE BIT!”
Billy Bob and Jethro make very salient points. At some point, Billy Bob asked what my persuasion is on the subject. Here was my diplomatic response:
“I like it a lot as a concept and how it forces a developer to write better code, but I haven’t found it to be very practical as far as deadlines and whatnot are concerned.”
You see, while I completely agree with the concerns Billy Bob and Jethro raised, I admit to having a soft spot for TDD.
TDD & Me
Back when I was a wee developer, I had dreams of growing into a great developer someday like some of those around me at the time. I was struggling mightily to get over ‘the hump’ though.
Junior devs know what I mean by ‘the hump”—I wish I could tell them that it’s all in their head and that there is no hump, but there is. That looming, ever-present barrier to the next level is, in my opinion, very much real and you will have to fight and claw to get over it. I’m sorry.
When I look back, I credit a focus on two main subjects at the time to helping me get over the hump: Design patterns and TDD.
One basic thing that TDD taught me is that it’s good to break up large methods into smaller, testable units. Actually, I probably knew that before, but TDD forces you into better practices. It changes your mindset in ways that are hard to explain. It seems to make you think about code in a different way than you had before, especially when you are new(ish).
TDD has a way of showing you that you may not yet truly understand the problem you are trying to solve. If you can’t write a test for it, you probably need to dig in and figure it out. Or it may mean you just need to have a conversation with a product expert, an architect or whoever it is that can shed more light on the issue.
Regardless, you will find that as you create your tests, they will gracefully increase your understanding of the task at hand.
Your tests also provide you immediate answers in regards to how your code will react to a consuming client. This allows you to respond to problems during development instead of after QA has sunk their claws into your work.
Beyond that, there are numerous lessons TDD teaches, such as encouraging dependency injection and simply writing cleaner, more elegant, and easier-to-maintain code.
Now hold on to your hats, it’s time for the plot twist.
After reading the above you may think that not only am I a big proponent of TDD but that I am a consistent practitioner of it as well. Neither one is true.
As mentioned, I love TDD for how it changes your mindset and the lessons it provides. Fortunately, those are lessons that stick with you whether you continue to use strict TDD or not.
No, I’m not a consistent practitioner of TDD mainly due to the issues mentioned by my brilliant colleagues, Billy Bob and Jethro. But also, deadlines. Some people say that TDD shortens development time. I’ve not found that to be the case.
My mom always used to say ‘everything in moderation’ and that’s a good rule to keep in mind when practicing Test Driven Development.
If you’ve never done TDD, force yourself to try it for a while. Learn the valuable lessons it has to teach you. Heck, some people fall in love with TDD and never code another line without it. Maybe that will be you and that’s great!
Just be aware that over-testing is analogous to painting yourself into a corner… in a very large room… with never-drying paint. Before you know it, it’s hard to move.
If you find it becomes difficult to refactor your code, you may be testing yourself into that proverbial corner.
Here are some things to keep in mind to avoid that situation.
- Your tests should wrap only public entry points into your code, basically the points where clients will interact with your code. By doing this, you should be able to refactor core logic without breaking your tests.
- Test behaviors rather than individual methods. The name of your test will let you know if you are on the right track here. For example, a test named
testProcessTransaction_sendsEmailWhenBalanceIsLowis testing that a certain behavior happens in a particular scenario. If, on the other hand, your test was named
testProcessTransaction, you’re likely testing everything that happens within
ProcessTransactionwith one test. That is a brittle test that is likely to break if any functionality is added to ProcessTransaction. See here for more info.
- Your tests should not have any flow control. In other words, they should not contain any conditional logic. If they do, that conditional code is likely duplicating some core logic, and if that logic changes, it will also have to change in your test.
I feel like I need to add a slight caveat. If it helps you to use TDD to create core logic, you can of course do that! But you may want to mark those as throw-away tests. Consider those tests as guides during development for your personal use and nothing more.
You can find many more rules online than the ones above regarding unit testing—like avoiding the usage of anything on the network (files, database, etc), for example. But the three rules above are specific to help you create more resilient tests that won’t have the Billy Bob’s and Jethro’s on your team pulling their hair out.
To wrap this up, let’s answer the question we started with: Is TDD a good practice?
In my opinion, yes. As with most things in development though, there needs to be a constant focus on avoiding the pitfalls. In TDD, those pitfalls are over-testing and creating brittle tests. Keep your eye out for those and TDD can absolutely be a powerful tool in your development tool belt.