Table of Contents
What is TDD?
2.1. What TDD is Not
2.2. What TDD IsAdvantages & Disadvantages
4.1. Advantages of TDD
4.2. Disadvantages of TDDDemonstration
5.1. Red Phase: Write a Failing Test
5.2. Green Phase: Write Code to Make the Test Pass
5.3. Refactoring Phase: Refactor the Code
5.4. Test the Validation
1. Introduction
1.1. A Brief History
Test Driven Development (TDD) was introduced by Kent Beck in the late 90s as part of the Extreme Programming (XP) methodology. XP is one of the agile development principles, which emphasizes constant interaction with the client, short development cycles, and responsiveness to changes. TDD, in particular, stands out because of its unique approach: writing tests before writing the actual code.
2. What is TDD?
2.1. What TDD is Not
TDD is not just a method for learning how to write tests. It is not about adding tests after the fact to validate already written code. TDD is much more than just a way to verify the code; it directly influences how the code is designed and written.
2.2. What TDD Is
TDD is a software development technique that guides the creation of code by writing tests first. It is an iterative approach to designing code that follows a very specific cycle. This cycle consists of three main phases:
Red Phase: Write a test that fails. This phase defines clearly what the code should accomplish.
Green Phase: Write the necessary code to make the test pass. The goal here is to satisfy the test without worrying about code quality.
Refactoring Phase: Refactor the code to improve it while keeping the tests intact. This phase allows for code optimization while ensuring that it remains functional.
3. How Long Does It Take to Learn TDD?
Learning the basics of TDD can be quick, and in fact, it only takes a few minutes to grasp the concept. However, mastering this technique takes more time and practice. Regularly applying the TDD cycle in real projects helps you better understand its nuances and get the most out of it.
4. Advantages & Disadvantages
4.1. Advantages of TDD
TDD offers several advantages that make it a popular method in agile development:
Better Designed Software: The code is written more thoughtfully and is often more modular, making it easier to maintain.
Better Test Coverage: The code is continuously tested throughout the development process, ensuring that it meets requirements from the start.
Clear Specification: Each test represents a clear specification of the expected behavior of the code, helping to better understand project requirements.
4.2. Disadvantages of TDD
Despite its advantages, TDD also has some drawbacks. The main issue is the time factor. Writing tests before the code may seem time-consuming, especially at the beginning of the process. However, in the long run, the benefits in terms of code quality and reduced bugs can outweigh this extra time.
5. Demonstration
5.1. Red Phase: Write a Failing Test
Let’s walk through a complete example of implementing TDD in Laravel. Imagine we want to create a feature that calculates the total of a shopping cart based on products and their prices.
We start by writing a test that defines what our functionality should do. The test will fail because the functionality doesn’t exist yet.
In the file tests/Feature/CartTest.php
, we write the following test:
<?php
namespace Tests\Feature;
use Tests\TestCase;
class CartTest extends TestCase
{
/** @test */
public function it_calculates_the_total_price_of_a_cart()
{
$cart = [
['name' => 'Product 1', 'price' => 100],
['name' => 'Product 2', 'price' => 200],
['name' => 'Product 3', 'price' => 300],
];
$total = $this->calculateCartTotal($cart);
$this->assertEquals(600, $total);
}
// We will define this method later
private function calculateCartTotal($cart)
{
// For now, it will return null
return null;
}
}
Here, we've written a test that checks if the method calculateCartTotal
correctly calculates the cart total. We've also defined a calculateCartTotal
method, which will return null
for now, causing the test to fail.
5.2. Green Phase: Write Code to Make the Test Pass
Next, we write the code needed to make the test pass. In this case, we will implement the calculateCartTotal
method in a dedicated class.
We will create a CartService
class in the app/Services
folder to handle this logic.
<?php
namespace App\Services;
class CartService
{
public function calculateCartTotal($cart)
{
$total = 0;
foreach ($cart as $item) {
$total += $item['price'];
}
return $total;
}
}
Next, we update our test to use the CartService
class.
<?php
namespace Tests\Feature;
use App\Services\CartService;
use Tests\TestCase;
class CartTest extends TestCase
{
/** @test */
public function it_calculates_the_total_price_of_a_cart()
{
$cart = [
['name' => 'Product 1', 'price' => 100],
['name' => 'Product 2', 'price' => 200],
['name' => 'Product 3', 'price' => 300],
];
$cartService = new CartService();
$total = $cartService->calculateCartTotal($cart);
$this->assertEquals(600, $total);
}
}
At this point, the test should pass because we have added the logic to calculate the cart total.
5.3. Refactoring Phase: Refactor the Code
Now that the test passes, we can refactor the code to make it cleaner and more maintainable. For example, we could move the cart total calculation logic into a Cart
model or add validations to ensure that the cart data is correct.
Let’s start by refactoring the CartService
class to include validation for ensuring each product has a valid price.
Here’s the refactored version of CartService
:
<?php
namespace App\Services;
class CartService
{
public function calculateCartTotal($cart)
{
$total = 0;
foreach ($cart as $item) {
// Validation to ensure the price is a positive number
if (!isset($item['price']) || $item['price'] < 0) {
throw new \InvalidArgumentException('Invalid product price');
}
$total += $item['price'];
}
return $total;
}
}
We’ve added a check to ensure each product has a valid price before adding it to the total.
Now we can also create a dedicated Cart
class to better organize the code.
5.4. Test the Validation
We now need to test that our code handles errors correctly. We will add a test to check that the calculation fails when a product has an invalid price.
Here’s an additional test to check this case:
<?php
namespace Tests\Feature;
use App\Services\CartService;
use InvalidArgumentException;
use Tests\TestCase;
class CartTest extends TestCase
{
/** @test */
public function it_calculates_the_total_price_of_a_cart()
{
$cart = [
['name' => 'Product 1', 'price' => 100],
['name' => 'Product 2', 'price' => 200],
['name' => 'Product 3', 'price' => 300],
];
$cartService = new CartService();
$total = $cartService->calculateCartTotal($cart);
$this->assertEquals(600, $total);
}
/** @test */
public function it_throws_an_exception_if_product_price_is_invalid()
{
$cart = [
['name' => 'Product 1', 'price' => -100], // Invalid price
['name' => 'Product 2', 'price' => 200],
];
$cartService = new CartService();
$this->expectException(InvalidArgumentException::class);
$cartService->calculateCartTotal($cart);
}
}
This test checks that if a product has an invalid price (negative in this example), an InvalidArgumentException
is thrown.
6. Resources
Here are some resources to deepen your knowledge of TDD:
7. Bibliography
Beck, K. (2003). Test-Driven Development: By Example. Addison-Wesley.
Fowler, M. (2009). Refactoring: Improving the Design of Existing Code. Addison-Wesley.
Erdogmus, H., & Williams, L. (2003). "Test-Driven Development: A Survey of Benefits and Challenges." Proceedings of the International Conference on Software Engineering.
van Deursen, A., & Moonen, L. (2001). "The Role of Test-Driven Development in Software Maintenance." Journal of Software Maintenance and Evolution: Research and Practice, 13(3), 129-151.
George, J., & Williams, L. (2004). "An Evaluation of Test-Driven Development in Industry: A Case Study." Proceedings of the 26th International Conference on Software Engineering.
In conclusion, TDD is a powerful approach to improving code quality and ensuring it meets requirements from the start. While it may seem challenging at first, the long-term benefits make it an essential method for modern developers.