TDD and BDD: What They Mean and Can You Use Both?
TDD and BDD are both software development methodologies in which automated tests play a vital role. They have similar-sounding names, work similarly, and generate similar results. They're not the same, though. But how much do the two approaches differ? What are their similarities? Should you pick one over the other or use both? These are the kinds of questions we'll answer throughout the post. Let's get started.
TDD and BDD: The Basics
We'll start our guide with some fundamentals on both TDD and BDD.
TDD: The What, Why, and How
Learn the basics about the TDD approach, starting with what it is in the first place.
Defining TDD
TDD stands for test-driven development. Despite this name, TDD isn't a software testing technique. Instead, it's a development methodology in which tests—more specifically, automated unit tests—are used to guide, or drive, the development of the production code.
How do you perform TDD? We'll see more on this later on, but the gist of it is that you develop software by working in short cycles in which you write a failing test and then the minimum amount of production code possible to make the test pass. Why would anyone develop software in such a weird way? The idea behind TDD is that following its cycle leads to cleaner code that's easier to read and change. As Kent Beck—the creator or "rediscoverer" of TDD—says in his book Test Driven Development: By Example:
Clean code that works, in Ron Jeffries' pithy phrase, is the goal of Test-Driven Development (TDD).
Why Do People Use TDD?
At first, the idea of writing tests before creating the code they're supposed to test seems absurd and backward. What could possibly be the benefits of doing that? We've already mentioned that the main goal of TDD is achieving clear, high-quality code that works. Let's expand on that by mentioning more benefits of TDD:
- You get testable code by default. Those who have experience trying to add unit tests to a legacy codebase know how hard that can be. If you follow the TDD process, the code you generate is testable by default.
- You get high code coverage as a default. If you only write production code to make a test pass, you'll always have total—or virtually total—code coverage.
- Defects are caught earlier. "Shift-left testing" is the idea that you should start testing as early as possible in the software development process so you can detect bugs sooner. If you write the tests before writing the production code, that's as early as you can be!
- TDD leads to a cleaner design with very modular, low-coupled code. That, along with the suite of unit tests you get, makes it easier to refactor code. So, people refactor more often, leading to higher-quality code.
- You get executable documentation in the form of unit tests. Would you like to have documentation for your software that doesn't get out of sync with the code it documents and that verifies itself? That's exactly what you get with a comprehensive suite of unit tests.
How Does TDD Work? It’s All About the Colors
When you do TDD, you follow a straightforward cycle sometimes called red-green-refactor. You start by writing a unit test to test some aspect of the workings of a given code artifact. The test fails because the artifact being tested doesn't exist yet. Unit test frameworks (the tools that run and verify the outcome of the tests) typically use the color red to signal a test failure. That's why the first phase in the TDD cycle is called red.
Here's an example of a unit test using C# and NUnit. Here, we're trying to drive the design of a type called Length:
Length expected = Length.FromMeters(8.0); // we define the expected result
Length actual = Length.FromMeters(5.0).Plus(Length.FromMeters(3.0)); // we execute the action
Assert.AreEqual(expected.Meters, actual.Meters); // we verify if the obtained result match what we expected
You then write the minimum amount of code possible to make the test pass. Since the color typically used to signal the success of a test is green, that's the name of this phase. We can make the test pass by "cheating." That is, we hardcode the expected value as the return:
public Length Plus(Length length) { return new Length(8); }
The next step is the refactor one. In this phase, you're allowed to change production code—but not add new code—to make the code better. You might remove some duplication, extract a new private method from an existing one to better organize code, and so on. The rule is that you can't break the test that's passing.
BDD: The What, Why, and How
Having covered the fundamentals of TDD, we'll now do the same with the other technique.
What Is BDD?
BDD means behavior-driven development. Dan North introduced the concept in an article published in 2006 due to challenges he was facing when implementing and teaching TDD.
BDD is an extension or evolution of TDD. It's meant to foster collaboration between the different actors involved in the software process, creating a ubiquitous language to have conversations about the project. Unlike TDD, which is a more developer-centric process, BDD is a higher-level and more business-oriented approach. The whole BDD process starts from a feature that generates value to the end user.
Why Do People Use BDD?
Since BDD comes from TDD, they share many of the same benefits. But BDD has some benefits of its own:
- BDD encourages creating a ubiquitous language in the software development process, facilitating communication inside the team and organization.
- BDD is a user-centric approach. The process starts by thinking of scenarios from the user's perspective. This helps to ensure that the team delivers what brings the most value for the end user.
- Scenarios in BDD are written in plain English. As such, they can serve as documentation for everyone, even people with no coding skills, unlike TDD tests, which require knowledge of code.
How Does BDD Work?
From a philosophical standpoint, BDD is about a way or mindset to create software, in which you're always thinking of features from the user's point of view. In more pragmatic terms, though, BDD is facilitated by using tools in a similar way to TDD. So, how is BDD performed?
The BDD process starts with a feature that you need to create. This is an example of what a feature could look like:
Feature: Add item to To-Do List
A user of the To-Do System would like to add a new item to the system.
Provided the user is logged to the system, and the item's info is valid, they should be allowed to add the item.
After defining a feature, the next step is to add one or more scenarios to it. A scenario describes a specific set of conditions and expected results from that feature. In our example, let's say the app shouldn't include features whose due dates are in the past. That's an important scenario to test. Here's what it could look like:
Scenario: User wants to add an item to the TO-DO system with a past due date
Given user is logged in
And today is April 10th 2021
When the user tries to add an item with a due date of April 5th
The addition of the new item should fail
BDD scenarios are written in plain English, using the Given-When-Then syntax. The next step is generating the skeleton for the automated tests using the BDD tool of choice. After that, the scenarios are executed, and they necessarily fail. Like in TDD, you haven't written the production code just yet.
After writing the minimum possible amount of code, the specifications will no longer fail.
TDD and BDD or TDD vs. BDD?
Though people might think that TDD and BDD are competitors, they're anything but. You can and should use both approaches when appropriate.
BDD can state the requirements in plain English, fostering collaboration between the different actors involved in the development process. However, the tests generated in the BDD process tend to be slower than TDD tests because they might be written at the integration level—in which different units are integrated, and real dependencies, such as the database, are used. The feedback they provide is also typically less precise. TDD unit tests, on the other hand, are usually fast and provide exact feedback. They can ensure that units work as expected in isolation.
That's why you should use both TDD and BDD. Each approach caters to a different need. While BDD is a higher-level, user-centric approach focused on encouraging conversation and collaboration, TDD is a development technique. It's lower level and developer-centric, and there's nothing wrong with that.
Since organizations are better off adopting TDD and BDD, how is that done in an increasingly remote workplace? The fact is that many organizations struggle when adapting agile processes—such as pair programming—to a remote scenario. It doesn't have to be that hard, though.
To help your organization in this journey, we've created a step-by-step guide: "Distributed Agile Teams: A 3-Step Guide to Success."
This post was written by Carlos Schults. Carlos is a consultant and software engineer with experience in desktop, web, and mobile development. Though his primary language is C#, he has experience with a number of languages and platforms. His main interests include automated testing, version control, and code quality.