There is no shortage of opinions on test-driven development (TDD) circulating the development community. I want to share my experiences driving design and development via unit tests and how eventual consistency of design can prove a powerful quality mechanism. I will also discuss the implications of what "it means" to be developing with a TDD approach.
Let's take a moment to understand what TDD means, not as a textbook definition, but practically applied. TDD is a design mechanism and considered a technical practice of Agile. It focuses on driving the design of a story or feature via the writing of unit tests. Writing unit tests first is not necessarily following TDD. The key is not writing tests first, but to drive your design decisions from these tests; a subtle yet significant distinction.
TDD is a design mechanism and a technical practice of Agile
The tests driving your design are at the unit level, which should be the most granular, basic functionality level. Focusing on the unit level and their tests, your design decisions become deferred and evolve to an "as need basis." By delaying design decisions, you are essentially able to make those decisions with more information. Intrinsically, you will generally know more later than you currently do now.
you know less now than you will later
The above explains why to utilize TDD. However, what impact does that have on a team? What does implementing TDD mean to the developer, team, and organization?
Let's look at an example and a common scenario with many Agile teams. Our team has just finished their sprint planning session. Stories have been sized and committed for the upcoming sprint. Now that we know which stories to complete and our sprint objective, it is time to break the stories down into tasks. Typically these tasks are also given estimates in hours or even days. This process could take up to as long as a few days. Now, I will make a bold statement by questioning the self-evident truth of task breakdowns: Why are we even doing this? What purpose does it serve, and what value does it add?
tasking stories: why are we even doing this?
Considering this process can quickly eat into a couple of days of the sprint, it would seem worth asking these sorts of questions. To find out, let's ask one additional question: what are we doing in the act of tasking out stories? The tasks represent the "how," not the "what" or "why." Suppose tasks are the "how," then we are essentially designing the implementation of the story! We are collectively architecting the story via a group design session. What makes it even worse is we typically use the implementation details to understand better what we are supposed to implement.
avoid describing the problem in the language of the solution
If we are using TDD to drive our design, then really TDD will tell us "the how." TDD will "organically grow" your "how," or design. So what impact does this have? It allows the team to focus on the "what" and "why" and not get caught up in the "how." It also means there is no need for these task breakdown meetings, giving the team back that precious time to focus on their purpose, delivering good working software.
favor focusing on the "what" and "why" instead of the "how."
This focus impacts not only the team but also the organization. It ultimately provides more thought and scrutiny to stories. Over time, this can lead to higher-quality stories. With more focus placed on the stories themselves, the demand for the story's readiness and clarity increases. The team can also provide more feedback on stories, further helping the product owner write better stories.
focusing on what a story is and why it is needed adds additional pressure for the story's readiness to be completed
TDD is a top-down approach to design and challenges us to change the way we approach software architecture. It does not mean a lack of forethought, but to defer decisions until as late as possible.
avoid making design decisions upfront
By driving developers to focus on the most granular level and only making their tests pass, there is a tendency to develop no more than necessary. What does this mean? It means there are less software bloat and unneeded/unused code in the codebase. It helps keep developers from over-engineering or writing code to cover more generic, "what if" type edge cases.
TDD helps developers, not over-engineer or focus on "what if" edge cases
Write the code to satisfy a test. Write tests to meet a story. A story is a feature from the end-users' perspective. Every line in the codebase should correspond to a component used to benefit a user of the software. If users of the software do not benefit in some way, directly or indirectly, then there is little to no value for it to live in the codebase.
written code should always benefit the user of the software, whether directly or indirectly
What is the eventual consistency of design? It is the notion that by implementing in small, iterative increments, the software's architecture will stabilize over some time. What are some of the real-world implications of this? The eventual consistency of a software's architecture helps manage the inconsistencies in developers' coding styles, preferences, and design choices, as well as sudden changes in direction or functionality.
eventual consistency of design is the notion that by developing in small, iterative steps, a software's system will stabilize over some time
Software is not static, and this is common sense. Upfront architecture produces a result of the design of the software. We stated that software is not static; no defined "final result." Software exists through a continuum of iterations, refactoring, and developers, a dynamic and, ultimately, evolutionary progression of code and features through time. These concepts are where the eventual consistency of design becomes incredibly essential and where TDD is successful.
software is a dynamic evolution of code and features through time
TDD affords iterative, rapid feedback and forces smaller increments of development. It provides a higher level of confidence to refactor and make further, small improvements to the software's codebase. Ultimately, it allows developers to be adaptive enough to evolve the codebase with changing features, direction, and design.
TDD allows developers to refactor with confidence!
Has TDD ever produced software with horrible design? Yes. Has upfront design ever made software with an awful design? Yes. If these are your questions, know they are not the right ones to be asking. Instead, ask this: "Is your design approach adaptive enough to effectively support fast-paced changes in design, direction, and features?" If your answer is not a resounding "Yes!" then TDD is one approach enabling you to develop with greater agility.