Test Driven Development with PHPUnit

Test Driven Development with PHPUnit
By Sam Holman
3rd December 2010

I think you’ll definitely find one of those eurika moments. You know, like when you first discovered revert and merge in your SCM. It just takes a little longer to get there with Test Driven Development, but the moment your test suite catches an error that would have otherwise made it to QA or even worse, production, you’ll instantly fall in love.

I regularly talk to two types of developer. Those that love unit tests, and those that think they take too much time to write, time that could be better spent actually implementing the code in question.

I will add a caveat at this point. As much as i love unit testing, i don’t write tests for the smallest of applications. Those jobs with just a couple of hours worth of dev time assigned, or “throwaway” apps. The value in TDD comes through the course of a reasonably sized project, and then over time when it’s maintained and developed further.

So presuming you’re beginning a new, sizable project, unit tests are your best place to start - and it’s really not that complicated.

There are a number of test frameworks for PHP, the biggest of which are probably PHPUnit and SimpleTest. My preference is for PHPUnit, which is the one i’ll be using here. So first thing’s first:

pear channel-discover pear.phpunit.de
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install phpunit/PHPUnit

I won’t get into the setup of pear, hopefully you know what you’re doing with your PHP configuration - if not you should probably have a look at the pear docs first.

A simple unit test usually follows these rules:

  • The class should be named SomethingTest
  • Extends PHPUnit_Framework_TestCase
  • Lives alone in a file called SomethingTest.php
  • Tests are defined as public methods called testSomething()
  • Test methods contain one or more assertions.

There are exceptions to some of these, or alternative ways to define things, but those are generally the basics.

Assertions are the nuts and bolts of unit testing. Basically you assert that you want the result of a particular operation to be equal to a given value. A simple (albeit useless) assertion might look like this:

$this->assertEquals(0, count(array()));

There are a number of assertion methods provided by PHPUnit, all of which can be found in the docs, but they include assertTrue, assertFalse, assertNull, assertEmpty, etc.

A simple, but complete, test class would look like this (this particular example taken from the PHPUnit documentation):

<?php

class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        $this->assertEquals(0, count($stack));

        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));

        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}

Now this example is testing some basic array functionality in PHP, but obviously what we want to do is unit test our own code.

TDD is all about “coding by contract”. Unit tests often feel like a chore when writing after the fact. I would certainly not discourage writing tests for an existing code base, and when TDD hasn’t been employed this is really the only way to go, but the idea here is to write your unit tests first. This tends to ensure that the actual implementation is as simple as possible because all you're trying to do is make your tests pass and nothing more. And in the end, the less code you write the less scope there is for error.

Writing a new test class

I’m going to imagine a new class for handling configuration values. This class doesn’t exist yet, but i’ve thought a little about what i need it to do, as an implementation of the registry pattern. In the full implementation i’d probably write functionality to load values into this class from ini files or similar, but i’ll leave that out for the purposes of an example.

I’d like to be able to set() and get() values from my Config class. The values can be of any type, but the option name should always be a string.

Time to define the test class:

Tests/ConfigTest.php

<?php

include_once '../application/Config.inc.php';

class ConfigTest extends PHPUnit_Framework_TestCase
{
    private
        $_config;

    protected function setUp()
    {
        $this->_config = new Config();
    }

    public function testNoOptionSet()
    {
        $this->assertFalse($this->_config->get('test_config'));
    }

    public function testSetOptions()
    {
        $this->assertTrue($this->_config->set('test_config', true));
        $this->assertTrue($this->_config->get('test_config'));

        $this->assertTrue($this->_config->set('test_another_config', 'hello'));
        $this->assertEquals('hello', $this->_config->get('test_another_config'));

        $this->assertTrue($this->_config->set('test_config_array', array()));
        $this->assertEquals(array(), $this->_config->get('test_config_array'));

        $this->assertFalse($this->_config->set(array(), 'test'));
    }
}

Obviously at this point the tests cannot possibly pass, as it’s creating an object and calling methods which don’t even exist yet, but the idea behind TDD is that it encourages the concept of programming to an interface instead of to an implementation.

Running a PHPUnit test is as simple as changing to the directory with your test(s) and running them by name:

samdev:~ samholman$ cd Tests
samdev:Tests samholman$ phpunit ConfigTest
PHPUnit 3.5.3 by Sebastian Bergmann.

PHP Fatal error:  Class 'Config' not found

As expected, there’s currently no class defined. But that’s good, step one of TDD complete!

Now to define the class:

application/Config.inc.php

<?php

class Config
{
    private
        $_options = array();

    public function __construct() {}

    /**
     * Set a configuration option
     *
     * @param string $var
     * @param mixed $value
     * @return bool
     */
    public function set($var, $value)
    {
        if (is_string($var))
        {
            $this->_options[$var] = $value;
            return true;
        }

        return false;
    }

    /**
     * Returns a config option or false if not set
     *
     * @param string $var
     * @return mixed
     */
    public function get($var)
    {
        return isset($this->_options[$var]) ? $this->_options[$var] : false;
    }
}

And now to run:

samdev:Tests samholman$ phpunit ConfigTest
PHPUnit 3.5.3 by Sebastian Bergmann.

..

Time: 1 second, Memory: 5.25Mb

OK (2 tests, 8 assertions)

Great, my implementation passes! Although this is only a very simple example. In the real world you’ll likely find a number of write -> refactor -> rerun test cycles before your class is actually up and running. I’d also like to note that with bigger classes you almost certainly wouldn’t write all of your tests before starting the actual implementation. It’s always worth tackling your testing in chunks, take it method by method if necessary.

The best thing is that when your implementation is done, you have a test class written which should ensure that it continues to work in the future. All you have to do is to make sure to run your tests any time you make changes to ensure that nothing has broken.

Let's imagine that someone (could be me, could be someone else) alters this Config class at a later date to enforce values to a string for some reason like this if (is_string($var) && is_string($value))

The test class would report:

PHPUnit 3.5.3 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) ConfigTest::testSetOptions
Failed asserting that  is true.

Tests/ConfigTest.php:22

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

So that regression bug has been caught before it had chance to cause any other issues.
I think it's also worth mentioning that your strategy for making intentional changes should be largely the same as in the initial instance. Modify your test classes to assert the new functionality, which will initially fail, then write the code so that all tests pass once again. Be very careful if you have to make changes to existing assertions, as this is the scenario where you're most likely to have compatibility breaks in your existing code base.

And this does now lead me onto the subject of test suites and Continuous Integration, which is an important part of the whole TDD process, but is definitely a large enough topic to warrant a completely separate post. If you're not sure what CI is at all, it's basically the process of automating a build - which can include everything from running your unit tests on every commit right through to packaging and deployment. Most CI tools can also provide metrics and statistics about build stability and code coverage and complexity, which is all incredibly useful. There's quite a few CI packages available, but the one we use at Label is called Hudson and it's quite frankly awesome.

Databases, the spanner in the works

Sooner or later you’ll come across the issue of database dependencies in applications and models which you’d like to unit test. Unfortunately there’s no universal solution to this, but there are a number of options, all of which are a little too much to go into here, but should be relatively simple to research with consideration for your specific requirements.

When it comes to DB resources we’ve found a couple of really viable options:

Fixtures with DB setup / teardown

This basically involves writing an SQL script to set up a database prior to running your tests.

Abstracting your data access

Typically using Data Access Objects to remove the actual database access from your app. Inside of the app you would add a DAO which has DB access, but for the purposes of unit tests simply mocks data internally.

Don't be discouraged

Please don’t let the relatively common problem of DB access put you off unit testing your code. It shouldn’t take too long to put the framework in place to support your database. By all means let me know if you’d like me to look into writing another article on this particular topic.

Additional test functionality

I’ve covered here a number of concepts to get you started, but there’s plenty more valuable functionality included in PHPUnit. I recommend the documentation on their site. A few really handy things to look into include dependencies, data providers, mocks and exception handling.

As always, let me know your thoughts on this article @labelmedia on twitter. Bye for now!

Like this? Sign up to our newsletter for more!

Add your own comment

© 2014 Label Media
We are recruiting