Recently I’ve been working with a mixed team of developers – contractors from a few companies with varied backgrounds. Our manager is sold on the value of test-driven development (TDD), but most of the team is anything but. The typical reaction to discussion of TDD is highly emotional and highly negative. This resistance is unexpected and perplexing to those of us who use TDD regularly – why wouldn’t you want to leverage a tool that improves your code quality and design, allows you to safely refactor throughout the course of your project, and gives you confidence to make changes late in your development cycle?
At Atomic Object, test-driven development is not optional and its value is not a matter of debate. We’ve seen what it brings to projects over and over, and it’s all too easy for us to spot the quality difference in code bases developed without tests. Would we ditch TDD for a different approach that allowed us to produce higher quality software more economically? Absolutely. Are there cases where we find manual testing to be a better fit than automation? Again, yes. But TDD is one of the best tools we have to efficiently produce reliable, malleable software. It’s central to our process. If you’re not sure that TDD is the way to go, here are a few reasons why you might want to take another look.
Confidence in Code Quality
You’ve probably worked on a project with no test suite or an untrusted test suite. When a manager asks you how stable the codebase is, how do you reply? Many times, the best you can do is give anecdotal evidence about what you’ve seen in your use of the application lately, or mention some statistics about the number of bug reports received in the last few weeks. Neither of these things are particularly reliable measures of application quality, and they’re both backward-looking.
What if you have a complicated problem to fix today? Or many smaller issues to resolve? How stable is your application after those changes? Often the only real answer is: “we’ll find out over the next couple weeks as people encounter problems.” This is not a good place to be, and it’s a place you don’t have to be if you are writing reliable tests for each feature you add to your software.
Confidence in Your Ability to Change your Software
If you find yourself without a way to confidently measure stability, what happens when release is a few weeks away? Generally development is locked down, only Sev 1 bugs can be fixed, and even then only after receiving approval from an oversight committee. Why? Because there’s no way to prove that the application is still working correctly after your changes, and no one wants to deal with the risk.
In TDD’ed projects, we do not have this late-cycle paralysis and can continue to add customer value throughout the release cycle. We don’t have to cringe when we merge changes late in the game or wonder what we might be breaking when we respond to a last-minute change request from management. The peace of mind late in a TDD release cycle is wonderful if you haven’t experienced it.
Ability to Refactor
I’ve been on plenty of projects where the team realizes that some area of the application has not been well cared for. The design has grown cluttered, hacks have crept in, and the code has become very difficult to read. This sad state is often allowed to fester because folks on the team are worried that the code has become so complex that it can’t be refactored without breaking functionality and there will be no good way to catch all the failures prior to committing the code. This is a terrible place to be, as the one constant for software is the need to change. When your code becomes brittle and arcane, it becomes more and more expensive to modify. Eventually someone will throw their hands up and ask for a rewrite (using TDD this time hopefully?).
TDD’ed code protects you from this sad predicament — you will have a safety net that allows you to refactor with confidence. This is important for even the cleanest code base, as requirements change throughout the course of a project. The eventual need for refactoring should be assumed for any code you write.
Increased Speed of Development
I’m not joking here — there are many reasons why a seasoned TDD’er will out-produce a developer writing code from the hip. The first is that a good set of tests will flush out logic problems without requiring the developer to launch the application and manually walk through the steps to exercise the feature. In my current C++ project, this is a real time-saver as running an entire build, launching the simulator, proceeding through the application start-up sequence, and finally getting to your piece of code is a lengthy process. Being able to flush out bugs prior to running those steps is a big win. What if you launch the feature, try three scenarios and find a problem with a fourth? Will you try the first three again after making your modifications? There’s a real cost to this manual verification during development, and it is time lost as soon as any further changes are made to the code.
A second win is that debugging becomes much simpler when your functionality is cleanly tested in small, isolated units. Rather than spend a lot of time in a debugger, you can let test failures point out your logic errors. Perhaps the most important reason why a TDD’er will outperform a developer who has no tests is that when you have an automated safety net, you can add or modify functionality without having to spend an unreasonable amount of time studying the code to convince yourself that you have every possible side effect in mind. You can make your change, run your test suite, and let the tests tell you whether your changes have had unanticipated side effects. This is especially crucial when modifying code written by others.
It’s Good for Your Resume
Knowing how to write quality tests for your code is less and less of an optional skill. Software is dramatically cheaper to build when development teams find their own bugs, and find them immediately when they’re added to the codebase. Take a look at requirements for the more interesting job postings these days, and you’ll see TDD listed more and more frequently as an expected skill.
To the Anti-TDD Crowd…
It’s my expectation that when people dismiss TDD, it’s because they’ve only seen trivial examples demonstrated and find that these examples don’t map easily to the complex projects they’re working on. “Sure you can TDD a mouse-trap, but that will never scale for my project!”
What I’ve found in practice is quite the opposite. TDD is generally overkill for trivial applications but really shines as projects grow in size and complexity. Learning how to write good tests is not trivial, and I think it’s best learned through practice rather than from books or talks. My advice is to find a pair who’s been through a TDD project or two, and give TDD a legitimate shot. I expect that within a couple months, you’ll find yourself wondering how those non-TDD guys do it.
I think for the most part you are definitely correct. Tests are a huge help in complex projects. They save developers time and headache. However, I’m going to take your last paragraph and go even further by saying don’t test built-in functionality. I see developers writing unit tests all the time for things that the framework they are using already has tests for. There isn’t any need to make sure that generated query is working as expected, because that should have already been covered by the framework’s unit tests. It appears to me as though a lot of newcomers to TDD fall into this trap of testing things they don’t need to, which could help explain why developers tend to be resistive to the idea of TDD.
Agreed with the benefits of TDD. I’m coding my personal project at home using TDD and it is definitely an exercise in discipline to code up the unit tests before the “fun stuff.” However, I have never felt greater confidence than I have when developing using TDD. It is a fantastic feeling.
Comments are closed.