If you have not heard of Test Driven Development(TDD), you should begin to familiarise yourself with it. Though PHP community is a bit late on TDD practice compared to other languages such as Ruby, once the benefits TDD were realized, it has become almost essential for a modern PHP developer.
TDD is a software development technique. The basic idea behind TDD is that, we create tests before we actually code any thing. Writing test against no code is more of a mindset shift than anything else. It is opposite of traditional coding habit, where we create code first, then manually run the unit to make sure it does what we intended manually. The benefits that TDD brings to us are enormous. At first it forces us to think about code design before we create any concrete code, then it allows us to refactor our code base without worrying about the side effect. It makes our code easy to maintain in the long run.
TDD consists of three phases: which are Red, Green and Refactor.
Red phase
In red phase, as the developer, we will plan out what the code will look like without actually writing it. This is to say, we will design our class or class methods, without implementing its details. Initially this phase is hard, it requires us to change our traditional habit of coding. But once we get used to this process, we will naturally adapt to it and realize that it helps us design better code. It is about changing our mindset, as we should focus on the input and output of the API, instead of the details of the code. The result of this phase is successful creation of red test.
Green phase
In green phase, it is all about writing the quickest piece of code to pass the tests. In this phase, we should not spend too much making the code clean or refactoring. Though we all want to write the most beautiful piece of code, that is not the task at hand in this phase. The result of this phase is green tests.
Refactor phase
In refactor phase, we focus on making the code clean. Since we have tests created above to guard bugs from side effects, we gain confidence for carrying out refactor. If by chance, a bug is introduced from refactoring, our tests will report it as soon as it appears. So the natural way of refactor is to run the test as soon as you have modified any code.
TDD lets us test drive our development cycle. When practicing TDD in PHP, obviously we need to define the kind of test we will do. The most common test in TDD is Unit Test which tests the smallest testable parts of an application it considers a unit, which is typically a class method.
Now imagine writing unit tests manually and building an automated method to run them. It is definitely a lot of work. Fortunately, there are already unit testing frameworks out there for us to use. Among a number of unit testing frameworks, PHPUnit is the most popular one and it is widely used in the PHP community.
Getting started with PHPUnit
Installation
The easiest way to install PHPUnit is via Composer. Open up your terminal and in your project folder, simply run composer require phpunit/phpunit . By default, the bin file of PHPUnit will be placed into vendor/bin folder, so we can run vendor/bin/phpunit directly from our project's root folder.
Your first unit test
Time to create your first unit test! Before doing so, we need a class to test. Let's create a very simple class called Calculator and write a test for it.
Create a file with the name of Calculator.php and copy the code below to the file. This Calculator class only has an Add function. :
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
}
Create the test file CalculatorTest.php, and copy the code below to the file. We will explain each function in details.
require 'Calculator.php';
class CalculatorTest extends PHPUnit_Framework_TestCase
{
private $calculator;
protected function setUp()
{
$this->calculator = new Calculator();
}
protected function tearDown()
{
$this->calculator = NULL;
}
public function testAdd()
{
$result = $this->calculator->add(1, 2);
$this->assertEquals(3, $result);
}
}
The last part of the task is to run PHPUnit and make sure it passes all tests. Navigate to the folder where you have created the test file and run the commands below from your terminal:
vendor/bin/phpunit CalculatorTest.php
You should be able to see the successful message as below:
PHPUnit 5.0.9 by Sebastian Bergmann and contributors.
. 1 / 1 (100%)
Time: 40 ms, Memory: 2.50Mb
Data Provider
When to use data provider
When we write a function, we want to make sure it passes a series of edge cases. The same applies to tests. This means we will need to write multiple tests to test the same function using different sets of data. For instance, if we want to test our Calculator class using different data, without data provider, we would have multiple tests as shown below:
require 'Calculator.php';
class CalculatorTest extends PHPUnit_Framework_TestCase
{
private $calculator;
protected function setUp()
{
$this->calculator = new Calculator();
}
protected function tearDown()
{
$this->calculator = NULL;
}
public function testAdd()
{
$result = $this->calculator->add(1, 2);
$this->assertEquals(3, $result);
}
public function testAddWithZero()
{
$result = $this->calculator->add(0, 0);
$this->assertEquals(0, $result);
}
public function testAddWithNegative()
{
$result = $this->calculator->add(-1, -1);
$this->assertEquals(-2, $result);
}
}
In this case, we can use data provider function in PHPUnit to avoid duplication in our tests.
How to use data provider
A data provider method returns a variety of arrays or an object that implements the Iterator interface. The test method will be called with the contents of the array as its arguments.
Some key points to keep in mind when using data provider are:
Once we know the key points, it is actually quite straightforward to use data provider. First, we create a new public method, which returns an array of a collection data as arguments of the test method.Then, we add annotation to the test method to tell PHPUnit which method will provide arguments.
Add data provider to our first unit test
Let's modify our tests above using data provider.
require 'Calculator.php';
class CalculatorTest extends PHPUnit_Framework_TestCase
{
private $calculator;
protected function setUp()
{
$this->calculator = new Calculator();
}
protected function tearDown()
{
$this->calculator = NULL;
}
public function addDataProvider()
{
return array(
array(1,2,3),
array(0,0,0),
array(-1,-1,-2),
);
}
/**
* @dataProvider addDataProvider
*/
public function testAdd($a, $b, $expected)
{
$result = $this->calculator->add($a, $b);
$this->assertEquals($expected, $result);
}
}
Now, run our test again and it should pass. As you can see, we have utilized data provider to avoid code duplication. Instead of writing three test methods for essentially the same method, we now have only one test method.
Test Double
When to use test double
As mentioned in the first part of this series. One of PHPUnit's powerful features is test double. It is very common in our code that a method of a class calls another class's method. In this case, there is a dependency between these two classes. In particular, the caller class has a dependency on the calling class, but as we already know from part 1, unit test should test the smallest unit of functionality. In this case, it should test only the caller function. To solve this problem, we can use test double to replace the calling class. Since a test double can be configured to return predefined results, we can focus on testing the caller function.
Types of test doubles
Test double is a generic term for objects we use, to replace real production ready objects. In our experience, it is very useful to categorize test doubles by their purpose. It not only makes it easy for us to understand the test case, but also make our code friendly to other parties.
Accordingly to Martin Fowler's post, there are five types of test double:
How to create test double
PHPUnit's method getMockBuilder can be used to create any similar user defined objects. Combining with its configurable interface, we can use it to create basically all five types of test doubles.
Add test double to our first unit test
It is meaningless to use test double in our calculator test case, since currently the Calculator class has no dependency on other classes, however, to demonstrate how to use test double in PHPUnit, we will create a stub Calculator class and test it.
Let's add a test case called testWithStub to our existing class:
public function testWithStub()
{
// Create a stub for the Calculator class.
$calculator = $this->getMockBuilder('Calculator')
->getMock();
// Configure the stub.
$calculator->expects($this->any())
->method('add')
->will($this->returnValue(6));
$this->assertEquals(6, $calculator->add(100,100));
}
We have introduced some basic usage of PHPUnit, which provides almost all the features we would need to create unit tests. You should always try to find more information from its official manual as you needed.
In this section, we will demonstrate the process behind TDD through a very simple example. You should concentrate on how the three phases of TDD are carried out in this example.
Suppose we are given a task of building a price calculator for our e-commerce system. The class we are going to develop will be PriceCalculator. Let's first setup the project's folder and file structure as well as its dependencies.
As usual, we will use Composer as our package manager and PSR-4 as our code standard. The only third party dependency is PHPUnit. To set things up, we will create a folder src for placing our source files, and a folder tests for placing test files. We will also create src/PriceCalculator.php and tests/PriceCalculatorTest.php respectively. Finally, we will create a composer.json file as below:
{
"require": {
"phpunit/phpunit": "^5.0"
},
"autoload": {
"psr-4": {
"Dilab\\Order\\": "src"
}
}
}
This file tells Composer to download PHPUnit and tells autoloader that our source code follows PRS-4 standard.
By running command composer install, we should end up with a folder structure as below:
.
+-- src
| +-- PriceCalculator.php
+-- tests
| +-- PriceCalculatorTest.php
+-- vendor
| +-- dependency-1
| +-- dependency-2
| +-- dependency-3
| +-- dependency-xxx
+-- composer.json
+-- composer.lock
The final piece we need for the setup is a phpunit.xml file to config PHPUnit. Let's create it as the folder root.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"<
<testsuites>
<testsuite name="Test Suite">
<directory suffix=".php">./tests/</directory>
</testsuite>
</testsuites>
</phpunit>
Our final folder structure should be as shown below:
+-- src
| +-- PriceCalculator.php
+-- tests
| +-- PriceCalculatorTest.php
+-- vendor
| +-- dependency-1
| +-- dependency-2
| +-- dependency-3
| +-- dependency-xxx
+-- composer.json
+-- composer.lock
+-- phpunit.xml
Red phase
At this phase we will plan how our API will look like and create failing test. In this example, the required API method is very simple. We just want a method that accepts an array as its parameter and calculate the total price. We will name this method total .
Let's create some tests in tests/PriceCalculatorTest.php file before we write any source code.
namespace Dilab\Order\Test;
use Dilab\Order\PriceCalculator;
class PriceCalculatorTest extends \PHPUnit_Framework_TestCase
{
private $PriceCalculator;
public function setUp()
{
parent::setUp();
$this->PriceCalculator = new PriceCalculator();
}
public function tearDown()
{
parent::tearDown();
unset($this->PriceCalculator);
}
/**
* @test
*/
public function object_can_created()
{
$priceCalculator = new PriceCalculator();
$this->assertInstanceOf('Dilab\Order\PriceCalculator', $priceCalculator);
}
/**
* @test
*/
public function should_sum_price()
{
$items = [
['price' => 100],
['price' => 200],
];
$result = $this->PriceCalculator->total($items);
$this->assertEquals(300, $result);
}
/**
* @test
*/
public function empty_items_should_return_zero()
{
$items = [];
$result = $this->PriceCalculator->total($items);
$this->assertEquals(0, $result);
}
}
We have created three tests for PriceCalculator:
Now if we run vendor/bin/phpunit from terminal, we should get error as expected as below:
Fatal error: Class 'Dilab\Order\PriceCalculator' not found in tests/PriceCalculatorTest.php
Green phase
The task of this phase is to make the failing tests above pass with the easiest but not necessarily the best code. The ultimate goal of this phase is the green message.
The implementation is fairly easy. All we need to do is to sum up the value with a foreach loop.
namespace Dilab\Order;
class PriceCalculator
{
public function total($items)
{
$total = 0;
foreach ($items as $item) {
$total += $item['price'];
}
return $total;
}
}
Now if we run vendor/bin/phpunit from terminal, we should get a green message as below:
PHPUnit 5.09 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 78 ms, Memory: 2.75Mb
Refactoring phase
This is the final phase of TDD, which we believe is the most valuable part of TDD. In this phase, we will take a look at the code we have written previously, and think of ways to make it cleaner and better.
We are using a foreach loop inside total method. It loops through $items array and returns the sum of the each individual element. This is actually a perfect use case of array_reduce method. Function array_reduce iteratively reduces the array to a single value using a callback function. Let's refactor our code by replacing foreach loop with array_reduce .
public function total($items)
{
return array_reduce($items, function ($carry, $item) {
return $carry + $item['price'];
}, 0);
}
If we run our tests again and they all still pass, we are good to go. Because we need to run the tests constantly to make sure refactoring does not break anything, it is important to keep our code fast.
We have cleaned up our code from five lines to two lines. There is no more temporary variable. The method has become easier to debug. There might not be apparent benefits for doing so in this example, but imagine this in a large scale project, even cleaning up one line of code could potentially make development easier.
This is the end of TDD. To emphasize again, the spirit of TDD is to let tests drive our development. Using PHPUnit in a project does not necessarily make it a TDD driven project. It is the three phases processes involved in the development that make it TDD.
Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.