What Is Test-Driven Development & How Does It Improve Software?
By Bennett Garner
Test-driven development (TDD) is a paradigm for writing minimal code in order to pass a pre-defined test or tests. TDD argues for the minimal amount of code to meet the requirements. When a developer practices test-driven development, the first thing on the task list is not to write production code. Instead, the developer begins by writing a simple test for the code. Once written, this test will (and should) fail. Now, the developer has a clear goal: write just enough code to get that one new test to pass. Upon a passing test, the dev cleans up and simplifies the code they’ve written, then they repeat the process by writing a new test.
Companies that practice test-driven development have reported a few key benefits to this approach. First, TDD practitioners release fewer bugs because code is constantly tested and refactored throughout the development process. Relatedly, developers practicing TDD say they need to use a debugger much less frequently because changes to the code are incremental. Finally, when the whole team practices TDD there exists a complete and up-to-date test suite for the entire build at any given time, allowing updates to confidently push to production.
The benefits of TDD seem strong and the philosophy itself is intuitive. However, there are challenges and pitfalls when practicing TDD. Research has yet to bear out all the anecdotal benefits that test-driven companies have seen. In addition, doing TDD wrong can slow down your development process and annoy developers. In this post, we’ll look at the fundamentals of test-driven development and how best to implement it.
How TDD Works in Practice
When practicing TDD, we write a test first, before anything else, and then we write the code to satisfy that test. One key point is that each cycle of test and code writing should be as small as possible. The two golden rules of TDD are:
- Write only enough of a unit test to fail
- Write only enough production code to make the failing unit test pass
It’s important that these cycles are quick and incremental in order to see the benefits of TDD. If you write a big unit test or multiple tests, then you’ll be coding for a while to implement a passing solution. Likely, you’ll deal with several layers of abstraction, create multiple functions and classes, and generally fall back into the same patterns as if you weren’t doing TDD.
Where TDD shines is when you think about every incremental change to the code base as a testable feature with a clear objective and output. The true benefit of TDD is it forces you to think about the application from the user’s perspective or the perspective of another developer using/reading your code. When you write a new test, you’ll be thinking, “What functionality does the user expect when they use this feature?” or “What value would a reasonable developer expect to be returned from this function?”
Thinking on this granular level leads to catching a lot of small errors or confusing and misleading code smells. The types of small things that would be easy to overlook when working on a big project become more apparent when you narrow your focus to passing a single test.
One of the most-voiced criticisms of test-driven development is that it’s slower than other paradigms. Indeed, because the tests have to be written up front and at regular intervals, TDD does take more time and energy toward the beginning of a project. However, companies that practice TDD say they save resources after release in the form of fewer bugs and less maintenance.
The “slowness” criticism comes because test-driven development does involve a lot of stop-and-think time built in. The process works in three phases under what’s called a “Red-Green-Red” development cycle.
1. Beginning Red Phase
The first stop-and-think phase is when the developer is writing the test. Here, the developer needs to make some important decisions that will impact the code they’re about to write.
- What are the functions and variables named?
- How many and what arguments get passed into the functions?
- What should the functions return?
- If information needs to be grouped together, should a new class be created?
The answers to these questions are part of writing the test, but they’re also the fundamental questions of writing good code. Because the developer has to think about and answer these questions ahead of time, they’ll be better prepared to implement a solution that’s intuitive and optimized.
2. The Green Phase
This is the time when the developer writes only enough code to make the test pass. Because the developer has already thought about the fundamental questions of how the solution will be implemented during the test writing phase, actually writing the code should be fairly straightforward.
If the developer has written a simple, small test, then the corresponding production code should also be simple and relatively small. If the developer is spending time in multiple areas of the application, changing or creating multiple classes or functions, then the test probably was too broad. Find something smaller to test first.
During the green phase, developers shouldn’t be concerned with cleanliness of the code. Focus on getting a working solution, and we’ll worry about making the code pretty later.
3. Final Red Phase
Now that the developer has written a working solution, it’s time for another stop-and-think phase. Instead of just plowing forward on new code, the developer takes a minute to think about how to optimize the solution they just wrote.
- Is all the code necessary?
- Do all variable and function names make intuitive sense?
- Can we separate concerns?
- Does each function do only one thing?
- Is this the simplest way to solve this problem?
This refactoring phase is essential to the success of TDD. It’s also the most often overlooked phase. When you refactor continuously, you’re making sure every component of your code is optimized before moving on to something else. In the future, when you call that function or use that class, you want it to be the optimized version of itself. In this way, you’re building your application on a solid foundation of optimized code, instead of hacking together some solution that works but will break at the first sign of a corner case.
Challenges & Pitfalls
As we’ve seen, there are many benefits to test-driven development. However, TDD isn’t without its challenges. It introduces more considerations into the early stages of the software development cycle. When poorly implemented, TDD can slow down and frustrate developers. It can also become a chore when companies insist on 100% test coverage or tests for trivial code.
For individual coders, beware of common traps that developers practicing TDD can get stuck in. Write only one small test at a time, not many. Run your test suite frequently, after every change. When you write your tests, make sure they’re small and test only one thing. Most importantly, don’t skip the refactor phase of TDD. Once your test is passing, spend the necessary time to optimize and shore up your solution. The future devs who interact with your code will thank you.
As a team, TDD presents challenges as well. It can be difficult to get everyone on board with the culture of TDD. When only a few developers on the team use TDD, the value drops significantly. Get buy-in from the whole team and make TDD training a core part of your onboarding for new devs.
In addition, a common reason why developers stop doing TDD is because the test suite becomes too large and unruly. Team leaders need to establish clear guidelines and responsibilities for test suite maintenance so that it’s fast and easy to run the tests.
Seeing the Benefits of Test-Driven Development
Test-driven development has surged in popularity for a reason. It’s adherents swear by its ability to produce cleaner, less buggy code. Making the investment in development time up front pays dividends over the long haul in reduced maintenance and stakeholder confidence. While establishing a TDD culture can be a challenge, the benefits are difficult to ignore.
Founded in 1991, Intertech delivers technology training and software development consulting to Fortune 500, Government and Leading Technology institutions. Learn more about us. Whether you are a developer interested in working for a company that invests in its employees or a company looking to partner with a team of technology leaders who provide solutions, mentor staff and add true business value, we’d like to meet you.
Originally published on Intertech Blog.