PHPUnit 3.3 测试版已经支持 BDD 了,但有两个主要的缺点:

1、测试代码和 Story 文本差距很大,不容易编写和阅读;
2、如果有失败的测试,没法显示出到底是哪一个测试没有通过。

经过简单的扩展,并且利用 PHP 5.3 对闭包的支持,可以采用下面的格式编写 BDD 测试代码:

  1. /**
  2. * 测试从账户中取现
  3. */
  4. class AccountHolderWithdrawsCashSpec extends PHPUnit_Extensions_Story_Runner
  5. {
  6.     /**
  7.      *
    @scenario
  8.      * 场景 1: 帐户有足够的资金
  9.      */
  10.     function AccountHasSufficientFunds()
  11.     {
  12.         //
    GIVEN
  13.         $this->given('帐户余额为 100', function (& $world) 
  14.             {
  15.                 // 由于 Account 对象必须属于一个 AccountHolder(帐户持有人),
  16.                 // 因此需要构造一个 AccountHolder 对象
  17.                 $account_holder =
    new AccountHolder();
  18.                 $account_holder->name = 'tester';
  19.                 // 创建一个 Account 对象,并设置余额为 $arguments[0]
  20.                 $world['account'] = new Account($account_holder);
  21.                 $world['account']->balance = 100;
  22.             })
  23.             ->and('有效的银行卡', function (& $world)
  24.             {
  25.                 $world['card'] = new CreditCard($world['account']);
  26.                 $world['card']->valid = true;
  27.             })
  28.             ->and('提款机有足够现金', function (& $world)
  29.             {
  30.                 // 确保 ATM 的余额大于帐户余额
  31.                 $world['atm'] = new ATM();
  32.                 $world['atm']->balance = $world['account']->balance + 1;
  33.  
  34.             })
  35.  
  36.         //
    WHEN
  37.         ->when('帐户持有人要求取款 20', function (& $world)
  38.             {
  39.                 $world['account']->drawingByATM($world['atm'], $world['card'], 20);
  40.             })
  41.  
  42.         //
    THEN
  43.         ->then('提款机应该分发 20', function (& $world, $action)
  44.             {
  45.                 $this->assertEquals(20, $world['atm']->last_dispense, $action);
  46.             })
  47.             ->and('帐户余额应该为 80', function (& $world, $action)
  48.             {
  49.                 $this->assertEquals(80, $world['account']->balance, $action);
  50.             })
  51.             ->and('应该退还银行卡', function (& $world, $action)
  52.             {
  53.                 $this->assertTrue($world['card']->isCheckedOut(), $action);
  54.             });
  55.     }
  56. }

看上去应该好看多了,呵呵。