PHPUnit是PHP的单元测试框架。单元测试在软件开发中越来越受到重视,测试先行编程、极限编程和测试驱动开发在实践中被广泛。利用单元测试,也可以实现契约式设计。
接下来,我们通过一个例子说明如何利用PHPUnit来实践测试驱动开发。
假设我们需要编写一个银行账户的功能:BankAccount
。该功能用于设置银行账户收支,存取现金,必须确保:
- 银行账户初始化时余额为0。
- 余额不能为负数。
在编写代码之前,我们先为BankAccout
类编写测试:
require_once'BankAccount.php';
classBankAccountTestextendsPHPUnit_Framework_TestCase
{
protected$ba;
protectedfunctionsetUp()
{
$this->ba = new BankAccount;
}
publicfunctiontestBalanceIsInitiallyZero()
{
$this->assertEquals(0, $this->ba->getBalance());
}
publicfunctiontestBalanceCannotBecomeNegative()
{
try {
$this->ba->withdrawMoney(1);
}
catch (BankAccountException $e) {
$this->assertEquals(0, $this->ba->getBalance());
return;
}
$this->fail();
}
publicfunctiontestBalanceCannotBecomeNegative2()
{
try {
$this->ba->depositMoney(-1);
}
catch (BankAccountException $e) {
$this->assertEquals(0, $this->ba->getBalance());
return;
}
$this->fail();
}
}
现在我们编写为了让第一个测试testBalanceIsInitiallyZero()
通过所需要的代码:
classBankAccount
{
protected$balance = 0;
publicfunctiongetBalance()
{
return$this->balance;
}
}
现在第一个测试可以通过了,第二个还不行:
phpunit BankAccountTest
PHPUnit 3.7.0by Sebastian Bergmann.
.
Fatal error: Call to undefined methodBankAccount::withdrawMoney()
为了让第二个测试通过,我们需要实现withdrawMoney()
、depositMoney()
和setBalance()
方法。这些方法在违反约束条件时,会抛出一个BankAccountException
。
classBankAccount
{
protected$balance = 0;
publicfunctiongetBalance()
{
return$this->balance;
}
protectedfunctionsetBalance($balance)
{
if ($balance >= 0) {
$this->balance = $balance;
} else {
thrownew BankAccountException;
}
}
publicfunctiondepositMoney($balance)
{
$this->setBalance($this->getBalance() + $balance);
return$this->getBalance();
}
publicfunctionwithdrawMoney($balance)
{
$this->setBalance($this->getBalance() - $balance);
return$this->getBalance();
}
}
现在第二个测试也能通过啦~
phpunit BankAccountTest
PHPUnit 3.7.0 by Sebastian Bergmann.
...
Time: 0 seconds
OK (3 tests, 3 assertions)
你也可以使用契约式设计的风格,只需使用PHPUnit_Framework_Assert
类提供的静态断言方法编写契约条件。下面例子中,如果断言不成立,就会抛出一个PHPUnit_Framework_AssertionFailedError
。这种方式可以增加你的代码的可读性。但是这也意味着你需要PHPUnit会成为你的运行时依赖。
classBankAccount
{
private$balance = 0;
publicfunctiongetBalance()
{
return$this->balance;
}
protectedfunctionsetBalance($balance)
{
PHPUnit_Framework_Assert::assertTrue($balance >= 0);
$this->balance = $balance;
}
publicfunctiondepositMoney($amount)
{
PHPUnit_Framework_Assert::assertTrue($amount >= 0);
$this->setBalance($this->getBalance() + $amount);
return$this->getBalance();
}
publicfunctionwithdrawMoney($amount)
{
PHPUnit_Framework_Assert::assertTrue($amount >= 0);
PHPUnit_Framework_Assert::assertTrue($this->balance >= $amount);
$this->setBalance($this->getBalance() - $amount);
return$this->getBalance();
}
}