Test Driven Development (TDD) is a software development practice.
The typical flow is:
- writing the test cases starting from the requirements;
- writing the code that solves the tests;
- refactoring the code to pass all tests.
Writing tests before the code, allows the developer to:
- focus on requirements;
- see if some requirements are missing or not detailed;
- focus about the edge cases (empty params, invalid params etc). This is very useful for thinking outside the "happy path".
This approach helps and supports the developer for creating a "better and more maintainable code" because it forces the developer making the code/functions testable from the beginning:
- splitting code in smaller and easier "pieces";
- favoring the single responsibility approach;
- favoring the code isolation (or encapsulation).
But, yes, fewer words and more code.
For our example, we are going to create a class with a static function for calculating the mean (the average).
Create your project from scratch
Create a new directory and jump into the new directory:
mkdir tdd
cd tdd
Install PestPHP
Now you can install the testing framework PestPHP.
PestPHP is built on top of the mature PHPUnit, and it aims to provide a nice and smooth interface for the developer that want to write tests.
composer require pestphp/pest --dev --with-all-dependencies
mkdir tests
./vendor/bin/pest --init
I'm going to skip the right configuration for PSR-4 autoloading, so I will create a file and include that in the test file via require
Create Stat class
Create your "stat.php" file with the class "Stat":
<?php
class Stat
{
}
Yes, I know, the class is empty. Before to write the method and the implementation, you need to create the new test file.
Create "StatTest.php" file in the "tests" directory. The convention to write tests in "tests" directory and naming the file with "Test.php" suffix, allows PestPHP to automatically find the right tests.
Create the test file
File "tests/StatTest.php":
<?php
require('./stat.php');
test('test mean', function () {
expect(Stat::mean([1,2,3]))->toEqual(2);
});
Before implementing the "mean()" method, we are expecting (expect()) that calling "Stat::mean()" with an array [1,2,3] as input parameter, it returns a value equal "2".
This is the happy path.
But I want also to think about "bad scenarios" for example if the user will call the method "Stat::mean()" with an empty array, I expect to retrieve a "null" value
test('test mean with empty data', function () {
expect(Stat::mean([]))->toBeNull();
});
Calling "Stat::mean()" with a non array parameter (for example an integer) I expect to have an exception:
test('test mean with not array', function () {
expect(
fn () => Stat::mean(42)
)->toThrow(TypeError::class);
});
If you run the tests:
./vendor/bin/pest
You will receive errors like "undefined method" because the class Stat is still empty, and it doesn't implement any methods.
Call to undefined method Stat::mean()
So now, jump into "stat.php" file and implement the method mean() that for now will always return the value 0.0.
<?php
class Stat
{
public static function mean(array $data): ?float
{
return 0.0;
}
}
If you run the test now, you will still receive errors.
The first one is about the assertion that expect to receive 2 as value. The current implementation is returning 0.0:
ā¢ Tests\StatTest > test mean() with array parameter
Failed asserting that 0.0 matches expected 2.
The second one is about the assertion that expect to receive "null" if the input parameter is an empty array. The current implementation is returning 0.0:
ā¢ Tests\StatTest > test mean() with empty array parameter
Failed asserting that 0.0 is null.
The test are failing, so now you need to focus on your implementation, on your code and implement the logic and making the test "green".
In your "stat.php" file in the "mean()" method, try to implement the mean:
- count the elements in the array;
- calculate the sum of the elements of the array;
- divide the sum with the count.
<?php
class Stat
{
public static function mean(array $data): ?float
{
$count = count($data);
$sum = array_sum($data);
return $sum / $count;
}
}
If you execute the tests, the only one that fails is the test that calls "mean()" method with empty array and raises a "Division by zero exception".
"Division by zero" is raised because the count is equal to 0. So, you need to check the length of the array, and if it is 0, you need to return "null".
<?php
class Stat
{
public static function mean(array $data): ?float
{
$count = count($data);
if ( ! $count) {
return null;
}
$sum = array_sum($data);
return $sum / $count;
}
}
If you execute the tests ...
All GREEN for us!
So, we:
- Wrote the test focusing on happy path and "bad scenarios";
- Wrote the code iteratively until all test passes.
And you, are you applying TDD practice in your development process?
Top comments (2)
great text, it demonstrates exactly what is needed to choose PestPHP as a testing framework for my project. the only thing i missed was a section talking about choosing what to test and what part not to test, and yes i know this is a complicated and controversial subject.
Reading on the live streaming, thanks!!