Support for BDD and Stories in PHPUnit 3.3

Sebastian Bergmann » 17 January 2008 » in New Features » 7 Comments

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.
Defined tags for this entry: , , , ,

Trackback specific URI for this entry

7 Comments to "Support for BDD and Stories in PHPUnit 3.3"

Display comments as (Linear | Threaded)
  1. stm
    17/01/2008 at 20:30 Permalink
    That implementation appears to defeat the purpose of Specs. As I understand them, the Spec should provide examples on how the functionality is used -- doing it as the examples site buries the example inside case statements inside other methods .. Seems less intuitive than that PHPSpec implementation that Pádraic Brady has started. (http://www.phpspec.org/) That being said, I'd love to see a Spec implementation I could run from the new Zend Studio for Eclipse PHPUnit integration. :)

    Reply

  2. Kevin
    19/06/2008 at 17:12 Permalink
    What concerns me about this example is the lack of reinforcement for negative tests. It's important to verify that code fails as expected, not just that it does what it's "supposed to" when conditions are right. OTOH - I believe that a well-written xSpec system provides a single point of test and documentation. For PHP developers, I believe that a good implementation of BDD doesn't have to be called PHPSpec, so long as it provides a consistent method of coding specification and tests while providing an easy method to generate specification docs and see where code is doing what isn't expected.

    Reply

  3. Jonavon
    19/08/2008 at 16:41 Permalink
    Is possible to create the methods with this template

    _whenAuthorizedUser
    _thenGiveAccess

    or use
    /**
    * @when
    * @given
    */

    In this way single methods don't exceed 20 lines. Additionally, in code documentation would be simplified.

    I appreciate the work on the project greatly.

    Reply

  4. Scott Payne
    04/09/2009 at 02:02 Permalink
    I know it's just an example (and I'm a year late to this party), but this:

    runThen()...
    for ($i = $world['rolls']; $i < 20; $i++) {
    $world['game']->roll(0);
    }

    is a very bad smell. You're altering your code under test in the assertion which defeats the purpose. It also means that the first spec (new game score == 0) will always pass.

    Better to get rid of that for loop altogether and see if everything still passes: it should be a characteristic of BowlingGame that a newly instantiated instance has a score of 0, not a characteristic of the test.

    Reply

  5. David
    16/02/2010 at 15:43 Permalink
    Can you add the ability to log story into the xml format, maybe --log-xml-with-story?

    I would like the story result to be appeared on my cruiseControl. Currently, the --log-xml doesn't include story result.

    Reply

  6. David
    17/02/2010 at 16:39 Permalink
    I also notice that there is no TestSuite for BDD spec. Is it possible to add it in as well.

    By the way, version 3.4 doesn't seem to have metrics generation for phpUnderControl.

    Reply

  7. Keith Brings
    19/07/2010 at 00:35 Permalink
    BDD is pretty exciting stuff. I like the extension.

    I'm working on some similar PHPUnit extensions that use a Cucumber/JBehave style approach and run test suites by parsing text files with DSL scenarios in them by mapping the various scenario steps to user specified step classes.

    I have a rough working prototype running but I'm ironing out a few issues, consolidating the API and adding some polish before posting my alpha source code to my google project page.

    I have a few images and some information up here http://code.google.com/p/phpconform/downloads/list

    I'd love to hear any thoughts you have on this if you have the time Sebastian.

    Reply

2 Trackbacks to "Support for BDD and Stories in PHPUnit 3.3"

  1. PHPDeveloper.org 17/01/2008 at 15:17
    On his blog today Sebastian Bergmann has posted about new functionality ...
  2. Sebastian Bergmann 15/09/2008 at 16:00
    PHPUnit 3.3.This release is a major improvement to the popular open source testing solution for PHP applications. It includes new features and bug fixes. Highlights of this release include:Improvements and Fixes for PHPUnit_Framework_AssertassertStringEqu

Add Comment


To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Submitted comments will be subject to moderation before being displayed.