TDD with PestPHP
Tags: tdd, php, tutorial, testing
Published: 2022-02-24 21:41:22

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:
```shell
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
<?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
<?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
```php
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:
```php
test('test mean with not array', function () {
    expect(
        fn () => Stat::mean(42)
    )->toThrow(TypeError::class);
    
});
```


If you run the tests:
```shell
./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
<?php

class Stat
{
    public static function mean(array $data): ?float
    {
        return 0.0;
    }
}
```

If you run the test now, you will still receive errors.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7git7mxw2uyt8t2f0135.png)

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
<?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".


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eekf5e793w93orgkplfx.png)

"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
<?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 ...

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zt94mj34hc78kbgifroc.png)

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?