Test driven development - Avoiding implementation mistakes
August 15, 2022
Many software teams have implemented test-driven development (TDD) to reduce rework and its associated costs. The rationale: The earlier you catch a problem in the software development lifecycle (SDLC), the less expensive it is to fix.
“Test-driven development” refers to a style of programming in which three activities are tightly interwoven: coding, testing (in the form of writing unit tests) and design (in the form of refactoring).
Here’s a rundown of the benefits, drawbacks, barriers to TDD adoption and common implementation mistakes.
The premise of TDD: You write the test before you write the code
With TDD, testing is at the forefront of people’s minds during product architecture, design and development. Software developers define up front what and how to test. What does the feature (code) need to do successfully to be considered complete? What tests does it need to pass to be called good?
Developers write these tests one at a time, starting with an easy case and building complexity from there, before coding the feature. This enables developers to make sure every possible scenario is covered. The feature must pass the old and new tests before a developer commits it to the code base.
How does TDD differ from earlier development approaches?
Before TDD, developers wrote code and QA wrote tests. These functions were silos. Even within a single team, developers used varied criteria to decide when to commit code to the base. Some developers felt testing was 100 percent QA’s job. Others wanted to make sure their code worked before sending it to QA. The individual developer decided how much testing to do (or not to do).
This led to inconsistent quality levels coming out of development, resulting in considerable rework, which impacted schedules and budgets.
What are the benefits of adopting TDD?
TDD is less expensive than the siloed approach. Why? When developers add new code to the base, the code works and doesn’t break the base.
The TDD mantra “keep it green” means the test result is always green before submitting code to the base (where it impacts others). If it’s not green, the developer keeps working on the code while it’s top-of-mind until it passes the test.
The silo approach, where developers toss code over the wall to QA, means that coders have already moved on to new functionality when QA finds bugs. The delay in finding the bugs can have downstream consequences—anything built later that relies on the problematic code may also need rework.
Another benefit: Tests document the code. When you read a test, you understand what the code should be doing, so you can validate that it’s working correctly.
Are there any downsides?
Some managers perceive that TDD takes longer and worry about how that will affect schedules for shipping new functionality.
It’s true that TDD requires extra time when building features. Software developers now have additional code (tests) to write and maintain. However, executives benefit from taking the long view. By spending a little more time on the front end of the SDLC, it’s possible to shorten the full SDLC significantly. Additionally, TDD improves the quality of software releases, which can lead to higher customer satisfaction and more revenue.
Another downside: Complex product development efforts may require writing additional proprietary frameworks to test how the product interacts with third-party tools and utilities. If you have a utility that you’ve customized in a specific way, you’ll need to create tests to make sure your product uses the utility correctly.
Common barriers to TDD adoption
The biggest barriers to adoption are engineering culture, individual developers’ preferences and the pressure to get products to market quickly.
In some organizations, TDD requires a significant shift in mindset. A change management expert can take the temperature of an engineering team to identify openness to TDD, areas of resistance and concerns. Sometimes, senior developers will say, “Testing is not my job. My superpower is creating new stuff, and I don’t want to be slowed down by writing tests.”
Some managers balk at TDD, feeling it will slow their team’s velocity. They want a high-quality, robust product but are sometimes willing to take shortcuts to get a release out the door.
To implement TDD successfully, management cannot allow elitist and speed-at-all-costs attitudes to persist. Engineering leaders need to set the expectation that all developers—even architects—are responsible for testing and code quality. If the architect is writing production code, then the architect is writing tests. Training can help get all team members on the same page and establish the testing ground rules everyone must follow.
Some organizations also wonder how to implement TDD on legacy projects. In this situation, start with the new features. Then, when refactoring portions of the legacy code, add tests for the newly refactored segments.
Some organizations make when implementing TDD?
There are three common errors that leaders can prevent with the right planning and communication:
1. Writing sloppy test code: Software developers need to treat test code and test utilities as production-level code. The test is going to live as long as the production code, so it needs to be well-written. Taking shortcuts can come back to bite you in the future.
2. Relegating testing to junior staff members: Many organizations put newer engineers on testing and senior people on production. In TDD, the testing is as important as the coding and needs oversight by experienced developers who can spot problems quickly. As a code base grows and becomes more complex, you want someone who knows what to look for and how to organize code. New-to-career programmers don’t have the experience to do this effectively, and you can end up with a mess.
3. Allowing broken tests to stay broken: Look up The Pragmatic Programmer or read Malcolm Gladwell’s book, The Tipping Point. Both offer examples of how ignoring something bad can cascade into a bigger problem. Some teams allow tests to break and then leave them broken, sprint after sprint. The damage from this approach compounds over time, and the longer you allow this to continue, the more expensive and time consuming it is to fix.
TDD solves a human limitation
Airline pilots use preflight checklists to eliminate the possibility of overlooking or forgetting something important. TDD serves a similar purpose.
Before TDD, a developer might have tested code manually, determined it passed and submitted it to the code base. But often there was some interaction that the developer didn’t think to test for. Submitting the code broke the base or caused other features not to work properly.
Lines of code execute based on different situations. It doesn’t take long to reach the point where developers can’t keep in their heads all the code and scenarios the code is supposed to handle. If multiple people on a team are contributing to the code base, no individual can track all the ways the software interacts.
TDD runs all the tests, old and new, on any new code. Some tests have interactions with the new code, others don’t. But developers do not have to keep this information in their heads, which reduces human errors and oversights. TDD frees developers’ working memory for other important problem-solving activities.
If a test is green, developers know their code works correctly and hasn’t broken anything. These tests confirm the old and new functionality work properly.