Test Driven Development, or TDD, is a software development methodology. In TDD a test is written first, then the code is written to pass this test.
In the traditional approach, the functionality is implemented first and tests are written later. In Test Driven Development this order is reversed. First, the expected behavior of the application is defined as a test. Then the functionality is created to meet this requirement.
Iterative process

TDD is an iterative process. It has three stages:
Red
The process starts with writing a test. At this stage, the test fails because the functionality does not exist yet.
Green
The next step is to write the functionality that passes the test. The goal is to write the minimum amount of code needed to pass the test. Code should not be written “just in case”.
Refactor
After the test passes, the code can be improved. Both the tests and the implementation code should be refactored.
This procedure is repeated many times. Instead of writing a large part of the system at once, functionality is developed step by step. For every new feature and every new requirement, the test is written first. Then the code is implemented.
Implementation
Assume that the goal is to create an online store. The store should have a shopping cart. The cart should have a list of products and return the total price of the products.
In TDD, the test should be created first. The cart should return the total price of all added products:
class CartTest {
@Test
void CartShouldReturnTotalPriceOfAllItemsInside() {
// given
Cart cart = new Cart();
Item item1 = new Item(15, "Shampoo");
Item item2 = new Item(13, "Soap");
cart.addItemToCart(item1);
cart.addItemToOrder(item2);
// then
assertThat(cart.totalPrice(), is(28));
}
At this stage, the test will not pass. This is the RED stage. The functionality, for example the totalPrice() method, does not exist yet. Then the minimum functionality needed to pass the test is implemented:
public class Cart {
private List<Items> items = new ArrayList<>();
int totalPrice() {
int sum = 0;
for (Meal meal: meals) {
sum += meal.getPrice();
}
return sum;
}
}
The method should now pass the test. This is the GREEN stage. Now the code can be improved, for example:
public class Cart {
private List<Items> items = new ArrayList<>();
int totalPrice() {
int sum = this.items.stream().mapToInt(item-> item.getPrice()).sum();
return sum;
}
}
The method has been improved. This was the REFACTOR stage. Now assume that there is a new requirement. If the total price is over 1000, the customer should get a 10% discount. The process starts again. First, a new test is created:
class CartTest {
@Test
void ShouldHave5PercentDiscountIfTotalPriceOver1000() {
// given
Cart cart = new Cart();
Item item1 = new Item(750, "Computer");
Item item2 = new Item(620, "Smartphone");
cart.addItemToCart(item1);
cart.addItemToOrder(item2);
// then
assertThat(cart.totalPrice(), is(1233));
}
This test will not pass again. This is the RED stage again. The method has to be changed:
public class Cart {
private List<Items> items = new ArrayList<>();
int totalPrice() {
int sum = this.items.stream().mapToInt(item-> item.getPrice()).sum();
if (sum >= 1000) {
sum = 0.9 * sum;
}
return sum;
}
}
The method should now pass the second test. This is the GREEN stage again.
The same process should be continued in the next iterations.
Pros and cons of TDD
In the traditional approach, the code is written first. Tests are added later. TDD forces tests to be written together with the code. Thanks to this::
- Bugs can be found more easily and more quickly
- Application development and scaling can be faster
- There are fewer regression bugs
- The code is more organized
- There are fewer unnecessary functions
- Refactoring is easier
- Tests can be used as documentation
At the same time, TDD also has disadvantages:
- At the beginning, code is usually written more slowly
- Badly written tests can give a false sense of safety
- Tests must be maintained together with the code
- Some features can be difficult to test
Summary
In TDD, tests are as important as the application code. Without good tests, it is hard to write good functionality. Tests have to be maintained and refactored, just like any other part of the code.
