Support for BDD and Stories in PHPUnit 3.3

PHPUnit_Extensions_Story_TestCase is a new extension for PHPUnit that has been contributed by Xait, a company that I visited last fall. It adds a story framework with a Domain-Specific Language (DSL) for Behaviour-Driven Development (BDD).

Start by writing a Story and a @scenario:

<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
 
require_once 'BowlingGame.php';
 
class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase
{
    /**
     * @scenario
     */
    public function scoreForOneSpareIs16()
    {
        $this->given('New game')
             ->when('Player rolls', 5)
             ->and('Player rolls', 5)
             ->and('Player rolls', 3)
             ->then('Score should be', 16);
    }
 
    // ...
}
?>

Each given(), when() and then() is a Step. The and()s are each the same kind as the previous Step. Steps get defined like this:

<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
 
require_once 'BowlingGame.php';
 
class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase
{
    // ...
 
    public function runGiven(&$world, $action, $arguments)
    {
        switch($action) {
            case 'New game': {
                $world['game']  = new BowlingGame;
                $world['rolls'] = 0;
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
 
    public function runWhen(&$world, $action, $arguments)
    {
        switch($action) {
            case 'Player rolls': {
                $world['game']->roll($arguments[0]);
                $world['rolls']++;
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
 
    public function runThen(&$world, $action, $arguments)
    {
        switch($action) {
            case 'Score should be': {
                for ($i = $world['rolls']; $i < 20; $i++) {
                    $world['game']->roll(0);
                }
 
                $this->assertEquals($arguments[0], $world['game']->score());
            }
            break;
 
            default: {
                return $this->notImplemented($action);
            }
        }
    }
}
?>

We can now ask PHPUnit to "tell our stories":

sb@vmware ~ % phpunit --story BowlingGameSpec
PHPUnit @package_version@ by Sebastian Bergmann.

BowlingGameSpec
 - Score for gutter game should be 0 [successful]

   Given New game
    Then Score should be 0

 - Score for all ones is 20 [successful]

   Given New game
    When Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
     and Player rolls 1
    Then Score should be 20

 - Score for one spare is 16 [successful]

   Given New game
    When Player rolls 5
     and Player rolls 5
     and Player rolls 3
    Then Score should be 16

 - Score for one strike is 24 [successful]

   Given New game
    When Player rolls 10
     and Player rolls 3
     and Player rolls 4
    Then Score should be 24

 - Score for perfect game is 300 [successful]

   Given New game
    When Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
     and Player rolls 10
    Then Score should be 300

Scenarios: 5, Failed: 0, Skipped: 0, Incomplete: 0.