PHP Unit Testing

I recently needed to investigate unit testing in PHP. I’m familiar with but not very well versed in PHP, and I’m certainly not a PHP aficionado, but a quick google search turned me on to PHPUnit by Sebastian Bergmann. The docs appear very complete and there’s a nice Getting Started guide to keep it simple. Using this tutorial and the accompanying GitHub repo, you can be up and running in a few minutes. Unfortunately, I ran into some problems because I am using PHP 5.3.3 (CentOS EL 6) and I was trying a literal copy and paste instead of using the provided repo. Don’t copy and paste, just use the repo. However, I managed to learn something by doing this.

PHP Versions

The simpler issue is PHP 5.3.3. I installed phpunit per the directions in the Getting Started guide. Here’s what happens when I clone the Money repo and run phpunit:

[rnelson0@build01 money:master]$ git remote -v
origin  git@github.com:sebastianbergmann/money.git (fetch)
origin  git@github.com:sebastianbergmann/money.git (push)
[rnelson0@build01 money:master]$ phpunit --bootstrap src/autoload.php tests/MoneyTest.php
PHP Parse error:  syntax error, unexpected T_CLASS, expecting T_STRING or T_VARIABLE or '$' in /home/rnelson0/php/money/tests/MoneyTest.php on line 55

The current version requires PHP 5.5. It’s okay, there’s an older version we can use in the 1.5 branch. Check it out, run phpunit again, and everything works.

[rnelson0@build01 money:master]$ git branch -a
  1.5
  1.6
* master
  remotes/origin/1.5
  remotes/origin/1.6
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/php-7
[rnelson0@build01 money:master]$ git checkout 1.5
Switched to branch '1.5'
[rnelson0@build01 money:1.5]$ phpunit --bootstrap src/autoload.php tests/MoneyTest.php
PHPUnit 4.8.2 by Sebastian Bergmann and contributors.

..............................S

Time: 665 ms, Memory: 18.75Mb

OK, but incomplete, skipped, or risky tests!
Tests: 31, Assertions: 50, Skipped: 1.

To Autoload, or not to Autoload

The second issue, where I copied the test code directly from the tutorial, was a little trickier. You are supposed to use the file src/autoload.php, but the tutorial does not provide it. You can see the full file in the repo, here’s an important snippet:

spl_autoload_register(
    function($class) {
        static $classes = null;
        if ($classes === null) {
            $classes = array(
                //...
                'sebastianbergmann\\money\\currency' => '/Currency.php',
                'sebastianbergmann\\money\\currencymismatchexception' => '/exceptions/CurrencyMismatchException.php',
                //...
                'sebastianbergmann\\money\\money' => '/Money.php',
                //...

This function maps the namespace’d classes to the files they are located in. I have not gone through the PHPUnit docs in great detail yet, but I haven’t seen instructions on generating this dynamically or crafting it manually. It’s certainly not part of the tutorial, so I decided to see if I could get around this with brute force. First, I generated a simple namespace and class, NewProject\Base.

<?php

namespace NewProject;

class Base {
  /**
   * @var integer
   */
  private $counter;

  /**
   * param integer $count
   */
  public function __construct($counter) {
    if (!is_int($counter)) {
      throw new \InvalidArgumentException('$counter must be an Integer');
    }
    $this->counter = $counter;
  }

  /**
   * Return the current counter value
   *
   * @return integer
   */
  public function getCount() {
    return $this->counter;
  }

  /**
   * Increase the counter and return its current value
   *
   * @return integer
   */
  public function increaseCount() {
    $this->counter++;

    return $this->counter;
  }
}

?>

The comments are there for PHPUnit. I think I’m doing it right, but I’m still new to this so it may not be accurate. This is also a very contrived class that exists just to do some testing, but for that purpose it’s great! Next, we need a class to do the testing. The name of the class is <Class>Test and it extends the PHPUnit_Framework_TestCase (there are others, but we’re starting small). Here’s the first draft:

<?php
namespace NewProject;

class BaseTest extends \PHPUnit_Framework_TestCase {
  /**
   * @covers NewProject\Base::__construct
   */
  public function testConstructor() {
    new Base(0);
  }

  public function testShouldFail() {
    new Base('string');
  }
}
?>

With unit tests, you want everything to pass, but I put the last one in because I wanted to make sure that an actual failure would be detected as a failure, not as a syntax error or something else that would bomb out the entire test suite. Here’s what happens when you run phpunit against that without an autoload file:

[rnelson0@build01 NewProject]$ phpunit tests
PHPUnit 4.8.2 by Sebastian Bergmann and contributors.

PHP Fatal error:  Class 'NewProject\Base' not found in /home/rnelson0/php/NewProject/tests/BaseTest.php on line 9

Well, shoot. It’s not loading the underlying class that it needs to test, and I don’t know how to generate an autoload file yet. Since it can’t find the class, I tried to see if I could force it to load that by adding a require() statement (emphasis on the additional line):

[rnelson0@build01 NewProject]$ cat tests/BaseTest.php
<?php
namespace NewProject;

require ('src/Base.php');

class BaseTest extends \PHPUnit_Framework_TestCase {
  /**
   * @covers NewProject\Base::__construct
   */
  public function testConstructor() {
    new Base(0);
  }

  public function testShouldFail() {
    new Base('string');
  }
}
?>
[rnelson0@build01 NewProject]$ phpunit tests
PHPUnit 4.8.2 by Sebastian Bergmann and contributors.

.E

Time: 221 ms, Memory: 18.25Mb

There was 1 error:

1) NewProject\BaseTest::testShouldFail
InvalidArgumentException: $counter must be an Integer

/home/rnelson0/php/NewProject/src/Base.php:16
/home/rnelson0/php/NewProject/tests/BaseTest.php:15

FAILURES!
Tests: 2, Assertions: 0, Errors: 1.

Lo and behold, that works! I’m sure at some point I’ll figure out how to generate the autoload, but this is good enough for now.

Summary

I’m well on my way to unit testing with PHP, thanks to Sebastian’s awesome framework. Thank you, Sebastian, you have taken much of the suck out of PHP!

You can find my test repo on github.

One thought on “PHP Unit Testing

  1. Pingback: PHP Unit Testing | Dinesh Ram Kali.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s