尝试行为驱动开发
2008-08-19 17:07
295 查看
原文是为《PHPer》电子杂志写的一篇稿子,这里摘取了一部分(主要是代码)发表。
BDD(行为驱动开发)是很热门的话题。对于热门话题我都是有好奇心的 ^_^
仔细看了一下各种资料,发现 BDD 真是个好东西。
以前写测试,都是针对功能来写测试。而 BDD 是针对系统行为的来写测试,实际上就是用测试定义了系统的行为。这样一来,写测试的过程实际上就是“设计”。在设计系统各个子系统应该具有的功能,这些功能的行为等等。
所以 BDD 是一种完完全全的设计方法,而不是测试方法(其实 TDD 也可以算是设计方法,不过争议很大)。
好了,理论性的东西不多说了,先看看一个实际的“故事”和其测试代码。
故事“帐户持有人提取现金”及场景“帐户有足够的资金”:
Story: 帐户持有人提取现金
As an [帐户持有人]
I want [从 ATM 提取现金]
So that [可以在银行关门后取到钱]
Scenario 1: 帐户有足够的资金
Given [帐户余额为 $100]
And [有效的银行卡]
And [提款机有足够现金]
When [帐户持有人要求取款 $20]
Then [提款机应该分发 $20]
And [帐户余额应该为 $80]
And [应该退还银行卡]
看上去很容易理解吧 :)
下面是测试代码:
<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
/**
* 测试从账户中取现
*/
class AccountHolderWithdrawsCashSpec extends PHPUnit_Extensions_Story_TestCase
{
/**
* @scenario
* 场景 1: 帐户有足够的资金
*/
function AccountHasSufficientFunds()
{
$this->given('帐户余额为', 100)
->and('有效的银行卡')
->and('提款机有足够现金')
->when('帐户持有人要求取款', 20)
->then('提款机应该分发', 20)
->and('帐户余额应该为', 80)
->and('应该退还银行卡');
}
// ...
/**
* 处理 given
*/
function runGiven(&$world, $action, $arguments)
{
switch($action)
{
case '帐户余额为':
// 由于 Account 对象必须属于一个 AccountHolder(帐户持有人),
// 因此需要构造一个 AccountHolder 对象
$account_holder = new AccountHolder();
$account_holder->name = 'tester';
// 创建一个 Account 对象,并设置余额为 $arguments[0]
$world['account'] = new Account($account_holder);
$world['account']->balance = $arguments[0];
break;
case '有效的银行卡':
$card = new CreditCard($world['account']);
$card->valid = true;
$world['card'] = $card;
break;
case '提款机有足够现金':
// 确保 ATM 的余额大于帐户余额
$world['atm'] = new ATM();
$world['atm']->balance = $world['account']->balance + 1;
break;
default:
{
return $this->notImplemented($action);
}
}
}
/**
* 处理 when
*/
function runWhen(&$world, $action, $arguments)
{
/**
* 在 when 中调用对象的业务方法
*/
switch($action)
{
case '帐户持有人要求取款':
// 从指定提款机使用指定银行卡取出现金
$world['account']->drawingByATM($world['atm'], $world['card'], $arguments[0]);
break;
default:
return $this->notImplemented($action);
}
}
/**
* 处理 then
*/
function runThen(&$world, $action, $arguments)
{
/**
* 在 then 中验证业务对象的状态是否符合标准
*/
switch($action)
{
case '提款机应该分发':
// 验证提款机的余额
$this->assertEquals($arguments[0], $world['atm']->last_dispense, "提款机应该分发 {$arguments[0]}");
break;
case '帐户余额应该为':
// 验证帐户余额
$this->assertEquals($arguments[0], $world['account']->balance, "帐户余额应该为 {$arguments[0]}");
break;
case '应该退还银行卡':
// 验证银行卡没有被 ATM 锁定
$this->assertTrue($world['card']->isCheckedOut(), '应该退还银行卡');
break;
default:
return $this->notImplemented($action);
}
}
}
基本上测试代码就是从“故事”文本转换过来的。只不过 PHPUnit 对 BDD 的支持还不是很成熟,所以看上去有点别扭。
跑一下这个测试看看结果呢:
> phpunit –story AccountHolderWithdrawsCashSpec
PHPUnit 3.3.0beta1 by Sebastian Bergmann.
AccountHolderWithdrawsCashSpec
- Account has sufficient funds [failed]
Given 帐户余额为 100
and 有效的银行卡
and 提款机有足够现金
When 帐户持有人要求取款 20
Then 提款机应该分发 20
and 帐户余额应该为 80
and 应该退还银行卡
Scenarios: 1, Failed: 1, Skipped: 0, Incomplete: 0.
呵呵,测试失败哦。可惜 PHPUnit 3.3 beta 1 还没法显示具体是哪一个测试失败了,所以要去掉 –stroy 参数再运行一次测试:
> phpunit AccountHolderWithdrawsCashSpec
PHPUnit 3.3.0beta1 by Sebastian Bergmann.
F
Time: 0 seconds
There was 1 failure:
1) AccountHasSufficientFunds(AccountHolderWithdrawsCashSpec)
帐户余额应该为 120
Failed asserting that matches expected value .
D:/www/517sc/test/AccountHolderWithdrawsCashSpec.php:100
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
这样就很清楚了,原来是帐户余额测试没有通过。知道问题所在,解决起来就很简单了。
BDD(行为驱动开发)是很热门的话题。对于热门话题我都是有好奇心的 ^_^
仔细看了一下各种资料,发现 BDD 真是个好东西。
以前写测试,都是针对功能来写测试。而 BDD 是针对系统行为的来写测试,实际上就是用测试定义了系统的行为。这样一来,写测试的过程实际上就是“设计”。在设计系统各个子系统应该具有的功能,这些功能的行为等等。
所以 BDD 是一种完完全全的设计方法,而不是测试方法(其实 TDD 也可以算是设计方法,不过争议很大)。
好了,理论性的东西不多说了,先看看一个实际的“故事”和其测试代码。
故事“帐户持有人提取现金”及场景“帐户有足够的资金”:
Story: 帐户持有人提取现金
As an [帐户持有人]
I want [从 ATM 提取现金]
So that [可以在银行关门后取到钱]
Scenario 1: 帐户有足够的资金
Given [帐户余额为 $100]
And [有效的银行卡]
And [提款机有足够现金]
When [帐户持有人要求取款 $20]
Then [提款机应该分发 $20]
And [帐户余额应该为 $80]
And [应该退还银行卡]
看上去很容易理解吧 :)
下面是测试代码:
<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
/**
* 测试从账户中取现
*/
class AccountHolderWithdrawsCashSpec extends PHPUnit_Extensions_Story_TestCase
{
/**
* @scenario
* 场景 1: 帐户有足够的资金
*/
function AccountHasSufficientFunds()
{
$this->given('帐户余额为', 100)
->and('有效的银行卡')
->and('提款机有足够现金')
->when('帐户持有人要求取款', 20)
->then('提款机应该分发', 20)
->and('帐户余额应该为', 80)
->and('应该退还银行卡');
}
// ...
/**
* 处理 given
*/
function runGiven(&$world, $action, $arguments)
{
switch($action)
{
case '帐户余额为':
// 由于 Account 对象必须属于一个 AccountHolder(帐户持有人),
// 因此需要构造一个 AccountHolder 对象
$account_holder = new AccountHolder();
$account_holder->name = 'tester';
// 创建一个 Account 对象,并设置余额为 $arguments[0]
$world['account'] = new Account($account_holder);
$world['account']->balance = $arguments[0];
break;
case '有效的银行卡':
$card = new CreditCard($world['account']);
$card->valid = true;
$world['card'] = $card;
break;
case '提款机有足够现金':
// 确保 ATM 的余额大于帐户余额
$world['atm'] = new ATM();
$world['atm']->balance = $world['account']->balance + 1;
break;
default:
{
return $this->notImplemented($action);
}
}
}
/**
* 处理 when
*/
function runWhen(&$world, $action, $arguments)
{
/**
* 在 when 中调用对象的业务方法
*/
switch($action)
{
case '帐户持有人要求取款':
// 从指定提款机使用指定银行卡取出现金
$world['account']->drawingByATM($world['atm'], $world['card'], $arguments[0]);
break;
default:
return $this->notImplemented($action);
}
}
/**
* 处理 then
*/
function runThen(&$world, $action, $arguments)
{
/**
* 在 then 中验证业务对象的状态是否符合标准
*/
switch($action)
{
case '提款机应该分发':
// 验证提款机的余额
$this->assertEquals($arguments[0], $world['atm']->last_dispense, "提款机应该分发 {$arguments[0]}");
break;
case '帐户余额应该为':
// 验证帐户余额
$this->assertEquals($arguments[0], $world['account']->balance, "帐户余额应该为 {$arguments[0]}");
break;
case '应该退还银行卡':
// 验证银行卡没有被 ATM 锁定
$this->assertTrue($world['card']->isCheckedOut(), '应该退还银行卡');
break;
default:
return $this->notImplemented($action);
}
}
}
基本上测试代码就是从“故事”文本转换过来的。只不过 PHPUnit 对 BDD 的支持还不是很成熟,所以看上去有点别扭。
跑一下这个测试看看结果呢:
> phpunit –story AccountHolderWithdrawsCashSpec
PHPUnit 3.3.0beta1 by Sebastian Bergmann.
AccountHolderWithdrawsCashSpec
- Account has sufficient funds [failed]
Given 帐户余额为 100
and 有效的银行卡
and 提款机有足够现金
When 帐户持有人要求取款 20
Then 提款机应该分发 20
and 帐户余额应该为 80
and 应该退还银行卡
Scenarios: 1, Failed: 1, Skipped: 0, Incomplete: 0.
呵呵,测试失败哦。可惜 PHPUnit 3.3 beta 1 还没法显示具体是哪一个测试失败了,所以要去掉 –stroy 参数再运行一次测试:
> phpunit AccountHolderWithdrawsCashSpec
PHPUnit 3.3.0beta1 by Sebastian Bergmann.
F
Time: 0 seconds
There was 1 failure:
1) AccountHasSufficientFunds(AccountHolderWithdrawsCashSpec)
帐户余额应该为 120
Failed asserting that matches expected value .
D:/www/517sc/test/AccountHolderWithdrawsCashSpec.php:100
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
这样就很清楚了,原来是帐户余额测试没有通过。知道问题所在,解决起来就很简单了。
相关文章推荐
- iOS尝试用测试驱动的方法开发一个列表模块【四】
- 优美的测试代码 - 行为驱动开发(BDD)
- Cucumber 行为驱动开发简介
- 行为驱动开发之四,为自动化测试(运行Cucumber)提速
- BDD(Behave Driven Development) - 行为驱动的开发 & Selenium
- 行为驱动开发之一,推广篇
- 基于Python的行为驱动开发实战
- 尝试使用测试驱动(TDD)开发
- 行为驱动开发之一,推广篇
- 转: 行为驱动开发之一,推广篇
- (译) 16-Mockito-行为驱动的开发
- [翻译]Behavior-Driven Development (BDD)行为驱动开发(一)
- [翻译]Behavior-Driven Development (BDD)行为驱动开发(二)
- iOS尝试用测试驱动的方法开发一个列表模块【五】
- 行为驱动开发(BDD)你准备好了吗?
- Rspec-ruby中的BDD(行为驱动开发)
- Rails 行为驱动开发(Behaviour-Driven Development)实践之一
- PHP测试篇:测试驱动开发和行为驱动开发
- BDD行为驱动开发笔录
- 转: 行为驱动开发之二,实施篇