PHP
Article

Basic TDD in Your New PHP Package

By Bruno Skvorc

How To Build Your Own PHP Package

In part 1, we set up our development environment, baked in some rules as inherited from The League, and created two sample but useless classes – Diffbot and DiffbotException. In this part, we’ll get started with Test Driven Development.

If you’d like to follow along, please read Part 1 or clone the part 1 branch of this tutorial’s code.

PHPUnit

We’ve covered PHPUnit to some degree before (1, 2, 3, 4, 5, 6), but it’s time we put it into real practice now. First, let’s check if it’s installed.

php vendor/phpunit/phpunit/phpunit

Running this command should produce a report that says one test passed. This is the test included in the League Skeleton by default and it asserts that true is, in fact, true. A coverage report will also be generated and placed into the build folder.

If you open this coverage report in the browser, you should see we have a low coverage score.

Now that we’re sure PHPUnit works, let’s test something. Currently, we have little more than getters and setters in our class and those aren’t generally tested. So what can we test in our current code? Well.. how about the validity of the provided token through instantiation?

First, let’s see the PHPUnit XML configuration file, phpunit.xml.dist. After changing the word “League” to “Diffbot”, this is what it looks like:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         backupGlobals="false"
         backupStaticAttributes="false"
         colors="true"
         verbose="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Diffbot Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist>
            <directory suffix=".php">src/</directory>
        </whitelist>
    </filter>
    <logging>
        <log type="tap" target="build/report.tap"/>
        <log type="junit" target="build/report.junit.xml"/>
        <log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
        <log type="coverage-text" target="build/coverage.txt"/>
        <log type="coverage-clover" target="build/logs/clover.xml"/>
    </logging>
</phpunit>

The attributes of the main element tell PHPUnit to make its report as verbose as possible, and to convert all types of notices and errors to exceptions, along with some other typical defaults you can look into on their website. Then, we define testsuites – sets of tests applying to a given app or scenario. One such suite is the main application suite (the only one we’ll be using), and we call it the “Diffbot Test Suite”, defining the tests directory as the host of the tests – you’ll notice the sample League test is already inside that directory. We also tell PHPunit to ignore all PHP files in the src/ directory (we only want it to run tests, not our classes), and finally, we configure the logging – what it reports, how, and where to.

Let’s build our first test. In the tests folder, create DiffbotTest.php. If you’re using PhpStorm, this is almost automated:

Remember to check that the namespace in the composer.json matches this:

"autoload-dev": {
        "psr-4": {
            "Swader\\Diffbot\\Test\\": "tests/"
        }
    },

Feel free to delete the ExampleTest now (as well as the SkeletonClass), and replace the contents of our DiffbotTest class with the following:

<?php

namespace Swader\Diffbot\Test;

use Swader\Diffbot\Diffbot;
use Swader\Diffbot\Exceptions\DiffbotException;

/**
 * @runTestsInSeparateProcesses
 */
class DiffbotTest extends \PHPUnit_Framework_TestCase
{

    public function invalidTokens()
    {
        return [
            'empty'        => [ '' ],
            'a'            => [ 'a' ],
            'ab'           => [ 'ab' ],
            'abc'          => [ 'abc' ],
            'digit'        => [ 1 ],
            'double-digit' => [ 12 ],
            'triple-digit' => [ 123 ],
            'bool'         => [ true ],
            'array'        => [ ['token'] ],
        ];
    }

    public function validTokens()
    {
        return [
            'token'      => [ 'token' ],
            'short-hash' => [ '123456789' ],
            'full-hash'  => [ 'akrwejhtn983z420qrzc8397r4' ],
        ];
    }

    /**
     * @dataProvider invalidTokens
     */
    public function testSetTokenRaisesExceptionOnInvalidToken($token)
    {
        $this->setExpectedException('InvalidArgumentException');
        Diffbot::setToken($token);
    }

    /**
     * @dataProvider validTokens
     */
    public function testSetTokenSucceedsOnValidToken($token)
    {
        Diffbot::setToken($token);
        $bot = new Diffbot();
        $this->assertInstanceOf('\Swader\Diffbot\Diffbot', $bot);
    }
}

In this extremely simple example, we test the Diffbot::setToken static method. We use PHPUnit’s DataProvider syntax to feed the values in a loop automatically (many thanks to Matthew Weier O’Phinney for correcting my course in this). This also lets us know which of the keys failed when testing, rather than just expecting or not expecting an exception. If we now run the test and look at the coverage, we should see something like this:

The achievement addict in me triggers, and suddenly I want to see as much green as possible. Let’s test instantiation:

public function testInstantiationWithNoGlobalTokenAndNoArgumentRaisesAnException()
    {
        $this->setExpectedException('\Swader\Diffbot\Exceptions\DiffbotException');
        new Diffbot();
    }

    public function testInstantiationWithGlobalTokenAndNoArgumentSucceeds()
    {
        Diffbot::setToken('token');
        $bot = new Diffbot();
        $this->assertInstanceOf('Swader\Diffbot\Diffbot', $bot);
    }

    public function testInstantiationWithNoGlobalTokenButWithArgumentSucceeds()
    {
        $bot = new Diffbot('token');
        $this->assertInstanceOf('Swader\Diffbot\Diffbot', $bot);
    }

These methods cover all the cases of the constructor – instantiation without a token and without a globally set one, instantiation with a globally set one, and instantiation with just the token param. However, running the test, we’ll see it fails. This happens because the Diffbot class still has the static property set from the previous test, ergo not lacking the default static token when used in the second test. This is a common problem with testing globals and statics. To get around this, we’ll make sure each of our tests in the DiffbotTest class is executed in a separate process. This will be much slower to execute, but will make sure each environment is fresh and unpolluted.

Add the following annotation above the class declaraiton, like so:

/**
 * @runTestsInSeparateProcesses
 */
class DiffbotTest extends \PHPUnit_Framework_TestCase

Now if you run the test and look at the coverage, you’ll notice we’re 100% green!

This is an anti-pattern of sorts, and usually indicative of something being wrong with the design of the class if it needs separate processes for testing, but I’ve yet to find a better approach to test this. The static property in the Diffbot class should be mutable for ease of use – if you have suggestions on improving this, I’m all ears. An alternative approach to solving this problem is building a reset method or some additional setters that you can use to manually return the class to its original state, but I avoid this approach in order not to pollute my class with test-related logic. Word is, this can be solved with backupStaticAttributes, too, but I’ve failed to make it work so far.

TDD

In TDD, you’re generally supposed to think of functionality, then test for it (and fail) and then implement it so it works. That’s where the testing drives your development, hence, test driven development. This is exactly what we’ll do in this section.

Diffbot, as a service, offers several default APIs:

  • Article API extracts structured data from article type content like news and blog posts
  • Product API extracts info about products. Send it to a product link and it’ll pull price, availability, specs, and more.
  • Image API gets you info about an image, or a set of images if you pass it a link to a page with several
  • Analyze API automatically determines which of the above three APIs to use, and auto applies it. It tries to use the approach that produces the most information when given a URL.
  • Video and Discussion APIs are still in development. Video is the same as Image API but for video files, while Discussion can extract conversation threads from forums, comment sections on various sites and social network posts, and more.

As evident by the documentation, each of the APIs returns a similar response (all return valid JSON), but the fields returned mostly differ. This is how I see the Diffbot class as a final product – it has methods for each API type, and each API type is a separate class we’re yet to develop. These API classes all extend one abstract API class which contains the setters for the common fields, but each API class itself contains its own settable fields, too. In a nutshell, I’d like to make the following approaches possible:

$diffbot = new Diffbot('myToken');

$productAPI = $diffbot->createProductAPI('http://someurl.com');
$productAPI
    ->setTimeout(3000)
    ->setFields(['prefixCode', 'productOrigin']);
$response = $productAPI->call();

// OR, LIKE THIS

$response = $diffbot
    ->createProductAPI('http://someurl.com')
    ->setTimeout(0)
    ->setPrefixCode(true)
    ->setProductOrigin(true)
    ->setHeaderCookie(['key' => 'value', 'key2' => 'value2'])
    ->call();

Testing Abstract Classes

To make the API subclasses, we’ll need a common API abstract class which to extend. But how do we test abstract classes without extending them? With Test Doubles. As you probably know, you can’t instantiate an abstract class on its own – it needs to be extended. Hence, if an abstract class can’t be instantiated, there’s no way to test its concrete methods – those inherited by all sub-classes. A test double can be used to make a fake version of an extended abstract class, used to then test only the abstract class’ concrete methods. It’s best to show you on an example. Let’s assume our API abstract will have a method setTimeout used to set the API request timeout on the Diffbot side. Let’s also assume that any number from 0 to max int is valid. In a true TDD fashion, let’s make the file tests/Abstracts/ApiTest.php with the content:

<?php

namespace Swader\Diffbot\Test;

use Swader\Diffbot\Abstracts\Api;

class ApiTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @return \PHPUnit_Framework_MockObject_MockObject
     */
    private function buildMock()
    {
        return $this->getMockForAbstractClass('Swader\Diffbot\Abstracts\Api');
    }

    public function validTimeouts()
    {
        return [
            'zero' => [0],
            '1000' => [1000],
            '2000' => [2000],
            '3000' => [3000],
            '3 mil' => [3000000],
            '40 mil' => [40000000]
        ];
    }

    public function invalidTimeouts()
    {
        return [
            'negative_big' => [-298979879827],
            'negative_small' => [-4983],
            'string ' => ['abcef'],
            'empty string' => [''],
            'bool' => [false]
        ];
    }

    public function testSetEmptyTimeoutSuccess()
    {
        /** @var Api $mock */
        $mock = $this->buildMock();
        $mock->setTimeout();
    }

    /**
     * @dataProvider invalidTimeouts
     */
    public function testSetTimeoutInvalid($timeout)
    {
        /** @var Api $mock */
        $mock = $this->buildMock();
        $this->setExpectedException('InvalidArgumentException');
        $mock->setTimeout($timeout);
    }

    /**
     * @dataProvider validTimeouts
     */
    public function testSetTimeoutValid($timeout)
    {
        /** @var Api $mock */
        $mock = $this->buildMock();
        $mock->setTimeout($timeout);
    }
}

We define some data providers just like in the first test class. Then, we have a method for creating our mock, so we can call it when needed. Next, we make a test method for each scenario – no timeout argument, bad argument and good argument. Only the bad one expects and exception.

If we run this test now, we’ll get an error:

This is not at all surprising – after all, we haven’t added the API class yet! Create the file src/Abstracts/Api.php with the content:

<?php

namespace Swader\Diffbot\Abstracts;

/**
 * Class Api
 * @package Swader\Diffbot\Abstracts
 */
abstract class Api
{
    
}

Running the test now produces a new error:

Whoa! We broke PHPUnit! Just kidding, we’re good. It’s complaining about not having a setTimeout() method, which is expected in the test – the mock is supposed to have it. Let’s change Api.php.

<?php

namespace Swader\Diffbot\Abstracts;

/**
 * Class Api
 * @package Swader\Diffbot\Abstracts
 */
abstract class Api
{
    /** @var int Timeout value in ms - defaults to 30s if empty */
    private $timeout = 30000;

    public function setTimeout($timeout = null)
    {
        $this->timeout = $timeout;
    }
}

Re-running the test, we get:

Now we’re getting somewhere. Let’s make one final implementation of our desired functionality. We edit the body of the setTimeout method, like so:

/**
     * Setting the timeout will define how long Diffbot will keep trying
     * to fetch the API results. A timeout can happen for various reasons, from
     * Diffbot's failure, to the site being crawled being exceptionally slow, and more.
     * 
     * @param int|null $timeout Defaults to 30000 even if not set
     *
     * @return $this
     */
    public function setTimeout($timeout = null)
    {
        if ($timeout === null) {
            $timeout = 30000;
        }
        if (!is_int($timeout)) {
            throw new \InvalidArgumentException('Parameter is not an integer');
        }
        if ($timeout < 0) {
            throw new \InvalidArgumentException('Parameter is negative. Only positive timeouts accepted.');
        }

        $this->timeout = $timeout;
        return $this;
    }

Along with logic, we added a docblock, and made the function return the instance of the class we’re using, so we can chain methods. Re-running the tests, all should pass. In fact, if we look at the coverage report, we should be 100% green.

Conclusion

In part 2, we started our TDD adventure by introducing PHPUnit and using it to develop some of our package’s functionality. You can download the full code of part 2 (includes part 1 code) from this branch. In the next part, we’ll continue building the package using the methods described here, and we’ll add in a new aspect – data mocking. Stay tuned!

Comments
mwop

I have technical feedback on the article.

First, regarding your additions to composer.json's autoload-dev section, a minor
point: you should append a "/" to the directory name. This normalization ensures
it doesn't attempt to match against a file prefixed with the value presented.

Second, for your first set of tests, you would be better served using data
providers and the setExpectedException method:

public function invalidTokens()
{
    return [
        'empty'        => [ '' ],
        'a'            => [ 'a' ],
        'ab'           => [ 'ab' ],
        'abc'          => [ 'abc' ],
        'digit'        => [ 1 ],
        'double-digit' => [ 12 ],
        'triple-digit' => [ 123 ],
        'bool'         => [ true ],
        'array'        => [ ['token'] ],
    ];
}

public function validTokens()
{
    return [
        'token'      => [ 'token' ],
        'short-hash' => [ '123456789' ],
        'full-hash'  => [ 'akrwejhtn983z420qrzc8397r4' ],
    ];
}

/**
 * @dataProvider invalidTokens
 */
public function testSetTokenRaisesExceptionOnInvalidToken($token)
{
    $this->setExpectedException('InvalidArgumentException');
    Diffbot::setToken($token);
}

/**
 * @dataProvider validTokens
 */
public function testSetTokenSucceedsOnValidToken($token)
{
    Diffbot::setToken($token);
}

(Ideally, you will also provide an assertion on the latter; just calling the
method without an error is not enough.)

Data providers are a built-in mechanism of PHPUnit designed specifically for the
scenario of running the same tests over different values. Using keys helps
identify which specific invocation caused a failure. (This same approach should
be used in your later example demonstrating testing an abstract class.)

The second tests, which test instantiation, should be split into three, one for
each scenario.

public function testInstantiationWithNoGlobalTokenAndNoArgumentRaisesAnException()
{
    $this->setExpectedException('DiffbotException');
    new Diffbot();
}

public function testInstantiationWithGlobalTokenAndNoArgumentSucceeds()
{
    Diffbot::setToken('token');
    $bot = new Diffbot();
    $this->assertInstanceOf('Swader\Diffbot\Diffbot', $bot);
}

public function testInstantiationWithNoGlobalTokenButWithArgumentSucceeds()
{
    $bot = new Diffbot('token');
    $this->assertInstanceOf('Swader\Diffbot\Diffbot', $bot);
}

The above ensures there are no side effects present in the different scenarios,
clearly delineats the different scenarios via discrete test cases, and leverages
PHPUnit's exception handling strategies.

Next, you suggest that developers use the @runTestsInSeparateProcess flag for
their tests, vs using setup or teardown logic. This is a terrible
recommendation; it substantially slows down test runs (and tests should execute
quickly!). Additionally, your stated reason (resetting static members) is an
indication of poor design; if static state is mutable, you should likely be
using concrete instances with instance methods instead.

swader

This is extremely valuable feedback, thank you very much! I'll update the article as soon as possible!

This is where the need for good testing guidance resources really shows up - I had been winging it so far, when I could have done better.

If anyone wants to write a "common testing pitfalls" post (much needed, as you can see), please let me know!

mwop

Clearly you've completely missed Chris Hartjes' blog posts and Grumpy Programmer guides to unit testing over the past couple years. smile

There are a ton of great posts happening on unit testing regularly, and I see them pop up on Planet PHP and twitter weekly. The content exists, and even quick searches for "phpunit," "phpunit tutorial," "phpunit tips," and "phpunit best practices" bring up a ton of good results immediately.

swader

I'll admit I've only recently obtained a copy of Grumpy's book, but Googling for resources is difficult when you don't know what you're doing wrong and what to Google for. I'll definitely be more careful in future installments, thanks for bringing it all to my attention.

swader

Regarding the above - what is your suggestion in approaching this? The static state is mutable only for ease of use later on, and the "instance" state is actually what is immutable - one cannot change the token on the instance. This makes using the class very straightforward, in my opinion, but goes against the flow, as you say, and drastically slows down tests. As someone whose opinion I value, I'd be interested in hearing what you think I could do to improve it without sacrificing this type of token setting.

llvdl

I would create a factory class to circumvent the static state, making the code easier to unit test, e.g.

class DiffbotFactory 
{    
    private $token;

    public function __construct($token) 
    {
        $this->token = $token;
    }

    public function createDiffbot() 
    {
        return new Diffbot($this->token);
    }
}

You can then instantiate multiple Diffbot instances without providing the token multiple times:

$token = 'xxxx';
$factory = new DiffbotFactory($token);
$api1 = $factory->createDiffbot();
$api2 = $factory->createDiffbot();

Hope that helps and thanks for the extensive write-up.

TomB

Nice article but it might be worth mentioning or at least pointing out that Vagrant/Homestead are not a dependency for PHPUnit. Setting up PHPUnit is actually very easy even without these tools smile

swader

The point is to be able to statically change the token mid-way, even after creating some Diffbot instances, so that all future ones use the new token, but with a minimal amount of code. While your factory could make this happen if we add a "setToken" method, I think that's too many layers for such a simple service - after all, the Diffbot class will returns subsets of APIs, so that's another "factory" layer right there (as you'll see in part 3, due out soon). Unless I'm misunderstanding something? Either way, adding a "runInSeparateProcess" flag to the static test only has reduced the entire test suite's running time to some 5 seconds and all is well now : )

I thought that was understood? We do demonstrate this in part one where it's added as a simple composer dependency, and it's mentioned in all the PHPUnit links at the beginning of the post. Where would you add this disclaimer? I.e., where do you feel like I'm implying the box is a PHPUnit dependency?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.