This article was last updated on January 25, 2024 to add comparison table and more detailed usecases of TDD and BDD
Introduction
Software testing is critical in the software development cycle and ensures that the developed products are reliable and of high quality. The quality of the application is vital for providing a satisfying user experience. Paying attention to the development methodology used in writing tests in an application is also important.
Test-Driven Development (TDD) and Behavior Driven Development (BDD) are two popular and effective methodologies developers use to write quality tests that benefit developers, users, product managers and stakeholders.
In this article, you’ll learn about Test-Driven Development (TDD) and Behavior Driven Development (BDD), including what they entail, their principles, advantages, disadvantages, how they work and their key differences.
Steps we'll cover:
- Overview of Test-Driven Development
- Step-by-step demo example of TDD implementation
- Overview of Behavior-Driven Development
- Step-by-step demo example of BDD implementation
- When to use BDD (Behavior-Driven Development)
- When to use TDD (Test-Driven Development)
- Comparison of TDD and BDD
Overview of Test-Driven Development
TDD is a repetitive and continuous process based on agile development methodology that involves creating test cases at each stage of developing an application to define the expected code behavior.
In TDD, developers first create a unit test case to showcase the desired behavior of the code before actually implementing it. If the test fails, they iteratively write new code until it successfully passes. Afterwards, they proceed to refactor the application's source code, which involves restructuring the code without introducing new features or compromising the original functionality of the application.
To implement TDD effectively, the process entails breaking down the application's functions and generating tests for each aspect. This approach ensures systematic and thorough testing and monitoring of the components.
A good example of TDD can be seen in building an Authentication system in an application.
According to the illustration above, the developer begins by identifying and defining the authentication system's requirements, including authentication methods such as OAuth, username, password, etc. The developer then writes a test that defines the expected behavior for one of the authentication system's components, such as the login functionality.
After that, the developer would run the tests, which would initially fail because the functionality had not yet been implemented. The developer then writes the code necessary to pass the test. The tests are then re-run, and the code is refactored. After refactoring, the tests are rerun to ensure they continue to pass.
After the login functionality has been validated, additional test cases for other functions, such as account verification, registration, and password reset, are created, and the TDD process is repeated.
Pros and cons of TDD
TDD offer several benefits. Still, it also has some drawbacks, as seen below:
Faster Development Cycle: TDD allows for the continuous delivery of software updates, and its architecture enables developers to quickly identify and fix bugs in their code. The rapid integration of updates promotes faster development and the delivery of high-quality software.
Improved Code quality: Writing test cases before writing the code enables developers to understand the desired functionality better and write well-structured code. Also, using the TDD approach makes it easier to refractor the section of the code and make it less buggy without affecting the existing functionality.
Time-consuming: TDD requires more time and effort in writing test cases before implementing the functionality, which may slow down the development process for projects with limited resources and short deadlines.
Rigid: The TDD approach of writing tests before implementing code is rigid because it is unsuitable for complex projects with constantly changing requirements.
Step-by-step demo example of TDD implementation
Let's see how TDD works in practice by building a simple app.
Prerequisites
To follow along with the tutorial, ensure you have the following:
- Nodejs installed
- Basic knowledge of JavaScript
- Terminal
- Code editor (Visual Studio Code editor)
To begin, create the project directory on your system by running this command in your terminal:
mkdir tdd-project
Next, change into the directory by running this command:
cd tdd-project
Open the project in your code editor, and in your project’s directory, run the following command to initialize a new Node.js project:
npm init
Next, you need to install a testing framework that will be used for performing unit testing in your project. Several testing frameworks are available depending on the programming language used to create an application. For example, JUnit is commonly used for Java apps, pytest for Python apps, NUnit for .NET apps, Jest for JavaScript apps, and so on. We’ll use the Jest framework for this tutorial since we are using JavaScript.
To install the Jest testing framework as a dev dependency in your project's directory, simply run the following command:
npm install jest --save-dev
Once the installation is successful, jest will be installed and added to your package.json file. Replace your test script with this:
"test": "jest"
Your package.json file should look like this:
Using the TDD approach to build your application demands that you start by writing the tests. Create a file called sub.test.js in the root directory of the tdd-project that will contain the tests. Jest uses a .test.js naming convention for files, so ensure your file has that extension.
Now, you can start writing your tests. Let’s say you want to create a small calculator app, and the first functionality you’d like to implement is the subtraction function. The Jest framework has its unique way of writing tests as defined in the documentation.
Jest uses a test()
function, which accepts a description as the first argument where you can describe the behavior you want to test and a callback where you can use an expect()
function and a toBe()
matcher that lets you define the expected behavior of your code and check if the behavior matches those expectations.
Let’s see the test()
function in practice. In your sub.test.js file, add the following test that will define the behavior of subtracting values with the subtract method that you will define later in the code:
const Calc = require("./calc");
test("subtraction", () => {
const calc = new Calc();
const sub = calc.subtract(20, 10);
expect(sub).toBe(10);
});
Next, we’ll try running the test, which will fail because you haven’t written the functionality yet. However, this is an essential step as the test failing shows that the test is testing the behavior.
Run the test with this command:
npm test
After running that command, the following will be displayed in your terminal:
Next, let’s write the code that implements the functionality. In your project’s root directory, create a file called calc.js and add the following code:
class Calc {
subtract(x, y) {
return x - y;
}
}
module.exports = Calc;
Here, we are creating a class called Calc
and adding a subtract()
method for the values we defined in the test case. Then we are exporting the class to use it outside of this module.
Now, you can rerun the test with this command:
npm test
If you implemented the subtract method correctly, then the test should pass as shown below:
You have successfully written your first test case for one functionality in your application. If your test case fails, you can correct and refactor your code. Then, you can write and run more test cases for other functionalities like sum, average, division and more.
Next, we’ll look at Behavior-Driven Development (BDD)
Overview of Behavior-Driven Development
BDD is another agile-based development process for creating tests that describe an application's expected behavior based on users’ expectations. Compared to TDD, BDD focuses on meeting business needs and user requirements rather than simply passing tests.
With BDD, developers can create products focused on meeting users' needs based on their interactions with the product. The BDD approach encourages collaboration between product managers (usually in charge of defining the product's requirements), developers and testers.
In BDD, developers can use testing tools such as Cucumber, SpecFlow, Behave, and others to plan and write tests in a language known as Gherkin, which helps define the product's business requirements or specifications in a structured format using keywords in human-readable syntax.
See an illustration of the BDD workflow below:
The BDD workflow, as illustrated above, consists of several stages, which are explained below:
Identifying User Features: This is the first stage in BDD where the features that need to be developed are identified. The features are described here based on the users' expectations.
Create Feature files: This stage entails creating files to document the application's features in a structured format that developers, product teams, and testers can understand using the Gherkin language.
Writing Scenarios: At this stage, test cases are defined in feature files with examples describing the expected behavior of the feature. The Gherkin language has a syntax for defining test cases.
Team Assessment:This is the stage at which the developers, product team, and testers collaborate to evaluate the feature files and scenarios created and defined in previous stages. The evaluation is performed to ensure that the defined scenarios align with the business requirements and the expectations of the users.
Writing Step Implementations: At this point, the implementation of the scenarios described in the Gherkin language begins. Developers write code in a specific programming language (e.g., JavaScript or Java) that depends on the BDD framework to map each step in the scenario to the corresponding actions that must be executed.
Test Automation: After the steps defined in the scenarios are implemented, automated tests are written to run the scenarios by simulating user interactions with the application and determining whether the behavior matches the specifications in the scenarios.
Test Validation and Reporting: At this stage, the automated tests are run, and the outcome of the scenarios (whether fail or pass) are recorded for the developers, product team, and testers to review.
Continuous Development: As developers receive new requirements from users or the product team, updates are made to the feature files and scenarios, and the entire cycle (i.e. the previous BDD stages) is repeated until the expected behavior is achieved.
BDD testing
BDD testing refers to testing practices within the BDD framework. In other words, It is the testing side of the BDD. These testing practices usually involve:
- Writing tests as executable specifications in Gherkin syntax (Given-When-Then format).
- Running these tests automatically before every code change.
- Collaborating with stakeholders (like product owners and business analysts) to define the scenarios. Focusing on testing the entire system behavior, not just individual units of code.
Pros and cons of BDD
BDD has several pros as well as cons. Here are a few:
Building Customer-centric Products: Products developed using the BDD approach are customer-centric because most of the features implemented are based on customer feedback. As a result, BDD ensures that the products align with and meet the customers' expectations.
Foster Collaboration and Transparency: The BDD approach provides transparency for developers, product teams, and testers to collaborate and understand the features defined, ensuring that they align with business requirements and user expectations.
Feedback-based: BDD depends on clear and effective communication between users and developers. When the communication channel is disrupted, the feature development process is hampered by a lack of collaboration between users and developers.
Step-by-step demo example of BDD implementation
In this example, you’ll learn how to create tests using the BDD approach.
Prerequisites
To follow along with this tutorial, you’ll need the following:
- Code editor (Visual Studio Code editor)
- Terminal
- Basic knowledge of JavaScript
- Nodejs installed
Let’s start by creating a directory for the project. Open your terminal and run the following command to create a folder called bdd-project:
mkdir bdd-project
Open the project in your code editor. In the root project’s directory, run the following command to initialize a new Node.js project:
npm init -y
Cucumber.js works with Node.js and is available as an npm module, so you’ll use it as your testing framework. Within your project directory, run the following command to install Cucumber.js as a development dependency:
npm install --save-dev @cucumber/cucumber
Next, create a folder called features with a file called auth.feature (the .feature extension is compulsory) that will contain the scenarios you’ll define. In the auth.feature file, you’ll use keywords provided by the Gherkin Syntax to describe the behavior of logging into the application from the users’ perspective as follows:
Feature: Login feature
As a customer
I would like to log into the application
So that I can gain access to my account
Scenario: Successful login
Given I am at the login page
When I type in my correct username and password
And click the "Login" button
Then I should be redirected to my home page
The keywords used above are explained below:
- Feature: The Feature keyword is the first keyword that describes the feature in a short text.
- Scenario: This Scenario keyword defines the specific test case that describes a particular behavior of the login feature.
- Given: The Given keyword specifies the initial state of the scenario.
- When: The When keyword describes the action performed by the user on the login feature.
- And: The And keyword adds more steps to the scenario that describe an action carried out by the user
- Then: The Then keyword specifies the expected outcome after the previous steps have been executed in this scenario
So far, you have defined steps in your feature file, next you’ll need to map the steps to their respective code implementation. To do this, create a file called step_implement.js and paste the following code:
const { Given, When, Then, And } = require("@cucumber/cucumber");
Given("I am at the login page", function () {
// write code that navigates to the login page
});
When("I type in my correct username and password", function () {
// write code to enter valid user credentials
});
When("click the {string} button", function (buttonText) {
// write code to click on the specified button
});
Then("I should be redirected to my home page", function () {
// write code to verify the redirection to the home page
});
You're importing the Given
, When
, Then
, and And
keywords from the Cucumber npm module you installed and writing the code to implement all of the steps and actions defined in the Scenario.
To execute the steps defined in the feature file, create another file called configure.js that will contain the Cucumber.js configuration:
module.exports = {
default: '--format-options \'{"snippetInterface": "synchronous"}\'',
};
You're configuring the default options for the Cucumber.js test runner here by specifying the format of the output generated by Cucumber to synchronous.
In your package.json file, set your test script to this:
"test": "cucumber-js"
Then, run the tests with the following command:
npx cucumber-js
Once you run the command, the outcome should be displayed in your terminal as follows:
The tests will pass once you add the appropriate code to implement the scenarios.
When to use BDD (Behavior-Driven Development):
- Collaborative requirements definition: Use BDD when you want to involve non-technical stakeholders (such as business analysts, product owners, or clients) in the definition of requirements. BDD's use of natural language allows for the creation of scenarios that are easily understood by all parties and everyone is on the same page.
- User-centric applications: BDD is particularly useful when the focus is on user behavior and ensuring that the system provides specific value to the user. It is ideal for projects where user stories are a key component of the development process, and the end user's experience is a priority.
- Complex business logic: When the project involves complex business logic that requires clear understanding and communication among stakeholders, BDD helps to clarify the desired behaviors through examples and ensures that all team members have a common understanding.
- Refinement of acceptance criteria: BDD is beneficial when the team requires a clear set of acceptance criteria that can be translated into automated tests. This ensures that the features meet the predefined behaviors before being considered complete.
When to use TDD (Test-Driven Development):
- Focus on quality and design: Use TDD when you want to ensure high code quality and a clean design from the start. TDD encourages small, incremental changes and refactoring, which can lead to a well-structured and less complex codebase.
- Developer-level feedback: When rapid feedback on the code's functionality is required, TDD provides immediate input to developers about whether their code works as expected. This can reduce the debugging time and speed up the development process.
- Regulatory compliance and documentation: In environments where there is a need for detailed documentation of the development process for regulatory compliance, TDD can provide an audit trail of tests that correspond to specific requirements of the system.
- Continuous integration environments: TDD is ideal in continuous integration environments where tests are run frequently. It ensures that new code does not break existing functionality, thus facilitating continuous improvement of the code with minimal disruption.
Comparison of TDD and BDD
So far, you've learned what TDD and BDD are, what they entail, and how they work. Let's look at how they differ in various aspects, as shown in the table below:
Aspect | TDD (Test-Driven Development) | BDD (Behavior-Driven Development) |
---|---|---|
Core Focus | Focuses on the implementation of functionality. | Focuses on the behavior and interaction of the system with the user. |
Scope | Typically limited to the developer’s scope; testing mainly at the unit level. | Broader scope involving stakeholders; tests at the system level with scenarios. |
Test Creation | Tests are written before code is implemented. | Scenarios are defined before tests are written; tests are derived from these. |
Language Used | Uses programming languages to write test cases. | Uses natural language, often English, to write scenarios. |
Automation | Tests are automated. | Scenarios can be automated using tools like Cucumber. |
Written By | Written by developers. | Written by or with stakeholders, such as product owners, developers, and testers. |
Test Examples | Should increment the count when the method is called. | Given a registered user, when they log in, then they see their dashboard. |
Communication | Primarily between developers or between developer and code. | Promotes communication between technical and non-technical stakeholders. |
Similarities | Both encourage writing tests before the actual code. | Both require writing tests before the actual code. |
Both aim to create a suite of automated tests. | Both aim to create a suite of automated tests. | |
Both methods emphasize the importance of testing in the development process. | Both methods emphasize the importance of testing in the development process. | |
Differences | TDD does not involve business stakeholders directly. | BDD encourages direct involvement of business stakeholders. |
Code-centric approach. | Behavior-centric approach, often through user stories. | |
Less emphasis on documentation that's understandable by business stakeholders. | Emphasizes documentation that doubles as living documentation for the system. |
Conclusion
Finally, you've reached the end of this article, where you learned about Test-Driven Development (TDD) and Behavior-Driven Development (BDD), including what they entail, their principles, their benefits and drawbacks, and how they differ. You also saw TDD and BDD in action in a demo application.