Thursday, March 17, 2011

Automatic Testing with PHP

Want to commit Friday night? Tired of the same bug appearing again, when already fixed it once? Constantly writing small test.php scripts to test behaviour?

Why not organize the whole thing, so you only have to press a button to ensure everything works as intended, or better yet, do it automatically whenever you commit something to your repository?

PHPUnit allows for all that and more, it can be intimidating at first, but if keep it simple, it’s not very hard and not very time consuming.

You need to install PHPUnit on your local computer and configure it with your IDE of choice (move along if your using notepad).

Configuration

Add a folder to your checkout/clone called “tests” containing the following:

library/
tests/
  My/
    Tests/
      Here.php
  bootstrap.php
  phpunit.xml

library in this case, is the directory containing the code you want to test, but pretty much any structure will work.

The bootstrap file contains the minimum to get your code (and your tests) running, usually something like an include path and autoloader:

$rootPath = realpath(dirname(dirname(__FILE__)));
$libraryPath = $rootPath . '/library';
$testsPath = $rootPath . '/tests';

$paths = array(
    $libraryPath,
    $testsPath,
    get_include_path()
);

set_include_path(implode(PATH_SEPARATOR, $paths));

spl_autoload_register(function($class) {
    return spl_autoload(str_replace('\\', '/', $class));
});

phpunit.xml is the configuration file, which PHPUnit will look for by default. This configures how your unit tests should be run, and among other things defines the name of your bootstrap file:

<?xml version="1.0"?>
<phpunit bootstrap="./bootstrap.php">
    <testsuite name="My Test Suite">
        <directory>./</directory>
    </testsuite>
    <groups>
        <exclude>
            <group>disable</group>
        </exclude>
    </groups>
    <filter>
        <whitelist>
            <directory suffix=".php">../library</directory>
        </whitelist>
    </filter>
</phpunit>

Now your ready...

Wait, how do you test your code? Well that's easy! In the My/ directory we add a file, I like to keep the exact same structure as in my library for unit-tests. But basically all the rules of normal code applies to your tests as well. E.g. refactor large classes into smaller ones, use inheritance where it makes sense etc.

Here is an example called tests/My/SimpleXMLTest.php (not our class, but we will borrow it for now). And while we are at it, we might as well just parse our PHPUnit configuration file (because it's already there!).

namespace My;

class SimpleXMLTest extends \PHPUnit_Framework_TestCase
{
    public function testMagicGet_BasicStringExample_ReturnsDirectoryValue()
    {
        $path = dirname(dirname(__FILE__)) . '/phpunit.xml';
        $xml = simplexml_load_file($path);
        $actual = (string)$xml->testsuite->directory;
        $this->assertEquals('./', $actual);
    }
}

There is a couple of things to note here:

  • I name the function according to some advice I found on the web: testMethod_Scenario_Assertions().
  • There are many assertX() methods, but they function pretty much the same. An expected value and the actual return value.
  • You should always try to expect a static value (first argument).
  • You should absolutely minimize the logic of your test (no if's, switches or loop!) helper functions are great though.
  • I keep my tests in the same namespace as my actual code (but not the same directory). Your IDE should ignore test classes for auto-completion.

Running the Tests

Will depend on what IDE you are using. In Netbeans Shift+F6 will run the selected test. Ctrl+F6 will run the test matching the selected implementation class.

Alternatively you can run the test suite manually:

$ phpunit <path-to-tests>

Sunday, March 13, 2011

Use that Namespaced Class

I've been spending a fair amount of time with php namespaces. So far experience has shown me that, it is much simpler to "use" classes explicitly, instead of namespaces.

A quick example:
use Namespace;
$obj = new Namespace\Class();
vs.
use Namespace\Class;
$obj = new Class();

Note that often you will use many more classes than one. Which would make "using" the namespace a one liner, and explicit use 5+ lines. However this has it's own advantages.

The Good:
  • You can see at a glance, in the top of a file, which dependencies it has.
  • You shorten class names even more, with no need for the namespace prefix.

The Bad:
  • The potential for name-clashes, but that is what aliasing is for, right?

It may seem like obvious advice, but I still did it the former way until I "got it".

Friday, March 11, 2011

Setting up Jenkins with Github

I just spend a lot more time cloning from github in Jenkins CI than was necessary.

After installing Jenkins, I started with Sebastian Bergmann's excellent php template for Jenkins was pretty straightforward and very well documented.

Cloning from Github

My first problem was with cloning my GitHub repository. I spend some time setting up ssh keys for authentication with Github. Now the easiest way would have been, to simply clone via the read-only url, as you therefore do not have to configure ssh.

Pick the right branch

After successfully cloning from Github, I kept getting some odd content. It turned out that it came from the gh-pages branch (wiki), that Github creates for you. Jenkins by default clones all the branches, one after another (look for ** in the configuration). Simply specifying the Branch Specifier as "master" fixed the problem.

Running the test suite

Lastly i had some trouble running the test suite. Console contained PHPUnit --help output, which basically means it doesn't know where the tests are. This is because, the template searches in the workspace root, instead of the tests folder. The solution was to add an argument, with the tests configuration file.

<target name="phpunit">
  <exec executable="phpunit" failonerror="true">
    <arg line="--configuration ${source}/tests/phpunit.xml ${source}/tests"/>
  </exec>
</target>

Missing coverage output

When the tests were running, Jenkins had trouble finding some xml files (build/logs/clover.xml and the build/coverage directory was empty). The php template for Jenkins recommends some logging options in phpunit.xml to generate these. However the logging methods that require Xdebug do not get executed without the extension. PHPUnit does not notify about this, but installing Xdebug fixed the problem.

Now everything is running smoothly, hurray!