Basic TDD in Your New PHP Package

Originally published at: http://www.sitepoint.com/basic-tdd-new-php-package/

This entry is part 2 of 2 in the series How To Build Your Own PHP Package

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.

How To Build Your Own PHP Package

<< Starting a New PHP Package The Right Way
Continue reading this article on SitePoint

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.

1 Like

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!

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.

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.

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.

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.

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 :slight_smile:

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?

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.