amatomu Afrigator

Test drive your PHP code

Lately I’ve ventured fully submerged into the world of Test driven development (TDD).

This might start out a bit scary, especially if you have only heard about it, but never done it yourself. There is a couple of good reads on the net, so you might want to start out by familiarising yourself with this exciting way of doing things.

Have a look at http://en.wikipedia.org/wiki/Test-driven_development or Google it.

What is test driven development?

Test driven development (TDD) is more a change in coding philosophy than anything else. Some of us might say: “…yip, I’ve written a couple of unit tests when I had some spare time at the end of my project.”

To be honest, this is a bit of the wrong way round method of doing things, kind of like building a house by putting on the roof first. I think Martin Fowler describes it best when he pointed out these three components.

  • Write a test for the next bit of functionality you want to add.
  • Write the functional code until the test passes.
  • Refactor both new and old code to make it well structured.

TDD is a branch of Agile development which was based upon the first stages of extreme programming, but has really become a coding methodology that changed the way in which a lot of projects are being done.

What do I need to get started?

UML

It is always good to have a basic Unified Modeling Language (UML) diagram of how you would like your objects/classes to work and behave, but believe me, after you analyzed it through testing, you would do a fair amount of refactoring.

Here we have some pretty UML for a very basic Message object which must create and delete messages. The Message implements a Message_Store object for message storage.

Message UML

PHPUnit

Now before you even attempt to write a line of code - go to http://www.phpunit.de/.

This will become your friend in days/months/years to come and you will learn to love it. :-)

Background:

PHPUnit was created by a German fellow called Sebastian Bergmann and has grown into a full fledge PHP testing framework. It was based upon SUnit, which was developed by the father of extreme programming, Kent Beck, to accommodate testing in Smalltalk.

Installing:

The easiest way to install PHPUnit would be via your PEAR Installer.

First you must register PHPUnit PEAR channel with your local PEAR environment.

pear channel-discover pear.phpunit.de

Now you can install PHPUnit to your local PEAR packages

pear install phpunit/PHPUnit

That should be it for now - PHPUnit should hopefully be installed on your system. According to the PHPUnit website the directory it is located in is /usr/lib/php/PHPUnit, but I instead found it inside /usr/share/php/PHPUnit on my Ubuntu installation. In all likelyhood it should be located inside the same directory as your PEAR installation.

Let the testing commence.

“Whoa, whoa, whoa!”, most developers would cry. “How can you possibly test code you haven’t written yet?”, they all sing together.

Here is where the mind shift starts. You should have a basic understanding of how your code should work/behave after you have done the UML for your class. So why not use the way you think your code should behave in your tests.

Your test class

First things first. If you have a look at your UML diagram you would see that there is a dependency between the Message class and Message_Store class.

Message UML

You should now write the test for how your Message object should behave.

//Include the base class for PHPUnit in order for your tests to work
require_once 'PHPUnit/Framework/TestCase.php';
 
//Include the Message class you are testing
require_once 'Message.php';
 
//Your Message Test Class
class MessageTest extends PHPUnit_Framework_TestCase
{
 
//Create a private class variable for re-use
private $oMessage;
 
/*
The setUp class function is native to PHPUnit and is executed infront of each
test function.  This will help you do things such as create a new object to work with before each new test.
*/
protected function setUp()
{
    $this->oMessage = new Message();
}
 
/*
The tearDown class function is reverse of the setUp() function and is executed after each test.
This will help you do things such as setting objects to a NULL value for re-use.
*/
protected function tearDown()
{
  $this->oMessage = null;
}
 
/*
First you test the construction of your Message object.
This isn't really necessary, since you know that a PHP constructor is suppose to work :-), but is just done
for completeness sake.
$this->assertType() tests to see if the object you created is of the type 'Message'.
*/
public function testMessageConstructed()
{
    $this->assertType('Message', new Message());
}
 
/*
Now you start testing all the public methods inside your class.  Since you are going to test the way you want
your code to work in, you will only be allowed to test the public accessible methods.  The tests will still cover any
private methods which is called from the public method tested.
This is kind of a double-barrel test. Since you can prove that the setStore is working if the getStore returns the
correct object.
$this->assertSame() will test if two different variables are of the exact same type
*/
public function testSetAndGetStore()
{
    $oPDO = new PDO('mysql:host=localhost;dbname=localdb', 'username', 'password');
    $oMessageStore = new Message_Store_Pdo($oPDO);
    $this->oMessage->setStore($oMessageStore);
    $this->assertSame($oMessageStore,$this->oMessage->getStore());
}
 
/*
You will now test if the save function will return a variable of bool type, either true if correctly saved or
false if incorrectly saved
*/
public function testSave()
{
    $oPDO = new PDO('mysql:host=localhost;dbname=localdb', 'username', 'password');
    $oMessageStore = new Message_Store_Pdo($oPDO);
    $this->oMessage->setStore($oMessageStore);
    $this->assertType('boolean',$this->oMessage->save('Hello'));
}
 
/*
You will now test if the delete function will return a variable of bool type, either true if deleted or
false if not deleted.
*/
public function testDelete()
{
    $oPDO = new PDO('mysql:host=localhost;dbname=localdb', 'username', 'password');
    $oMessageStore = new Message_Store_Pdo($oPDO);
    $this->oMessage->setStore($oMessageStore);
    $this->assertType('boolean',$this->oMessage->delete(1));
}
 
}

What does our test class tell us?

That’s it - we now have a couple of basic tests to cover our Message class. The important thing about writing tests is that you should cover all public functions used in the class you are testing. At the moment these tests shouldn’t run, but it will give you a clear idea of where to start and is great for debugging whilst writing code.

Already there is a bit of a discrepancy of what is happening in the unit tests and UML. The way we test our code, is the way we would like our objects to behave. In the UML we assumed that the the message store could be part of the message object, but as our tests showed us - there might be a more elegant way using a storage object, which would allow for more pluggable, loosely-coupled code.

If we passed a storage object into the Message object itself, after a while the message object will become bloated and unusable, since now you have to cater for your different storage objects within the Message object. For e.g. create a saveToFileSystem function and a saveToDatabase function, where some functions might become redundant when using a specific storage object.

So instead of passing a PDO storage object directly to the message, you could have a Message_Store object which interfaces to your various storage objects, such as a PDO or filesystem etc.

Thus turning:

$oMessage = new Message();
$oPDO = new PDO('mysql:host=localhost;dbname=localdb', 'username', 'password');
$oMessage->setStore($oPDO);

Into:

$oMessage = new Message();
$oPDO = new PDO('mysql:host=localhost;dbname=localdb', 'username', 'password');
$oMessageStore = new MessageStore($oPDO);
$oMessage->setStore($oMessageStore);

By creating a Message_Store class, rather than building extra code onto your Message object, you reverse any dependencies that might occur within the Message object. All this means is that for you can now create a message object, and pass a interfaced storage object to it, thus allowing your code to become more flexible, without building layers of complexity itself into the Message object.

Refactor your code.

Now update your UML to include an interface to the Message_Store object.

Message UML

Let’s build our message class according to the tests and updated UML diagram.

class Message
{
 
    private $oMessageStore;
 
    public function __construct($oMessageStore=Null)
    {
        $this->oMessageStore = $oMessageStore;
    }
 
    public function setStore($oMessageStore)
    {
        $this->oMessageStore = $oMessageStore;
    }
 
    public function getStore()
    {
        return $this->oMessageStore;
    }
 
    public function save()
    {
        return $this->oMessageStore->create($this);
    }
 
    public function delete($sMessageId)
    {
        return $this->oMessageStore->delete($sMessageId);
    }
 
}

Now your Message class is starting to look elegant. Now you can create the interface.

interface Message_Store_Interface
{
public function __construct($oStorage);
public function create($oMessage);
public function delete($sMessageId);
}

You can now implement the interface in the various Message store objects.

class Message_Store_Pdo implements Message_Store_Interface{
 
    private $oPdo;
 
    public function __construct($oPdo)
    {
        $this->oPdo = $oPdo;
    }
 
    public function create($oMessage)
    {
        $sSql = "insert into message_store (message)
        values (?)";
        $oSql = $this->oPdo->prepare($sSql);
        $oSql->bindParam(1, $oMessage->sMessage);
        $oSql->execute();
        return (bool) $this->oPdo->lastInsertId();
    }
 
    public function delete($sMessageId)
    {
        $sSql = "DELETE FROM message_store
        WHERE message_id = ?";
        $oSql = $this->oPdo->prepare($sSql);
        $oSql->bindParam(1, $sMessageId);
        $oSql->execute();
        return (bool) $oSql->rowCount();
    }
 
}

The advantages of Test Driven Development and Unit Testing

Running the unit tests

Now the big moment has arrived. Time to run your test. Assuming there is no syntax errors the test should execute fine.
Run this by going to your command prompt and enter the following

phpunit tests/MessageTest.php

If you saved the tests to MessageTest.php in the tests directory, everything should execute fine and the following would be displayed.

dewald@dewald-desktop:/var/www/mobi$ phpunit tests/MessageTest.php
PHPUnit 3.3.14 by Sebastian Bergmann.
 
....
 
Time: 0 seconds
 
OK (4 tests, 4 assertions)
dewald@dewald-desktop:/var/www/mobi$

You have compiled your tests and ran it succesfully. :-) Whoohoo - if your tests gave an error, just follow the debug messages and fix your code. Easy as that.

You can also create reports to see if you have achieved acceptable code coverage by running.

phpunit --coverage-html /tmp/tests/ tests/MessageTest.php

The reports should can be found in the /tmp/tests/ directory and should look something like this:

Test Report

In the End

In the beginning of the article I wrote that Martin Fowler said the following about Test Driven Development:

Write a test for the next bit of functionality you want to add.

We wrote all our test for the functionality of our Message object in the way which we would like our object to behave. We then test the results of that object.

Write the functional code until the test passes.

We wrote the functional code for the classes to make it work as we determined through our tests.

Refactor both new and old code to make it well structured.

After writing the tests, we realised that our code has some short-comings and dependencies, which we could reverse in-time, since we saw the way it was supposed to work in unit tests meant refactoring our UML and inherently we would have needed to refactor our code.

What now?
TDD doesn’t come naturally. Especially if you were used to years of a different kinds of development techniques. It comes through practice and experience.

I’ll suggest you read through the PHPUnit manual and examples, as to see how flexible the testing framework can be, also as a start, write some unit tests for your old code, and you might be surprised just how efficient you could have been. :-)

Enjoy.

  , , , , , ,

back to index

Leave a Reply