用phpunit实战TDD系列php
假设你已经 安装了phpunit.html
咱们从一个简单的银行帐户的例子开始了解TDD(Test-Driven-Development)的思想。java
在工程目录下创建两个目录, src
和test
,在src
下创建文件 BankAccount.php
,在test
目录下创建文件BankAccountTest.php
。c++
按照TDD的思想,咱们先写测试,再写生产代码,所以BankAccount.php
留空,咱们先写BankAccountTest.php
。bootstrap
<?php class BankAccountTest extends PHPUnit_Framework_TestCase { } ?>
如今咱们运行一下,看看结果。运行phpunit的命令行以下:函数
phpunit --bootstrap src/BankAccount.php test/BankAccountTest.php
--bootstrap src/BankAccount.php
是说在运行测试代码以前先加载 src/BankAccount.php
,要运行的测试代码是test/BankAccountTest.php
。测试
若是不指定具体的测试文件,只给出目录,phpunit则会运行目录下全部文件名匹配 *Test.php
的文件。由于test
目录下只有BankAccountTest.php
一个文件,因此执行this
phpunit --bootstrap src/BankAccount.php test
会获得同样的结果。编码
There was 1 failure: 1) Warning No tests found in class "BankAccountTest". FAILURES! Tests: 1, Assertions: 0, Failures: 1.
一个警告错误,由于没有任何测试。命令行
下面咱们添加一个测试。注意,TDD是一种设计方法,能够帮助你自底向上地设计一个模块的功能。咱们写测试的时候,要从用户的角度出发。若是用户使用咱们的BankAccount
类,他首先作什么事呢?必定是新建一个BankAccount的实例。那么咱们第一个测试就是对于 实例化 的测试。
public function testNewAccount(){ $account1 = new BankAccount(); }
运行phpunit,意料之中地失败。
PHP Fatal error: Class 'BankAccount' not found in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5
没有发现BankAccount
类的定义,下面咱们就要写生产代码。使测试经过。在src/BankAccount.php
(后面称之为源文件)中输入如下内容:
<?php class BankAccount { } ?>
运行phpunit,测试经过。
OK (1 test, 0 assertions)
接下来,咱们要增长测试,使得测试失败。若是新建一个帐户,帐户的余额应该是0。因而咱们添加了一个assert
语句:
public function testNewAccount(){ $account1 = new BankAccount(); $this->assertEquals(0, $account1->value()); }
注意value()
是BankAccount
的一个成员函数,固然这个函数尚未定义,做为使用者咱们但愿BankAccount
提供这个函数。
运行phpunit,结果以下:
PHP Fatal error: Call to undefined method BankAccount::value() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 6
结果告诉咱们BankAccount
并无value()
这个成员函数。添加生产代码:
class BankAccount { public function value(){ return 0; } }
为何要让value()
直接返回0,由于测试代码中但愿value()
返回0。TDD的原则就是不写多余的生产代码,恰好让测试经过便可。
运行phpunit经过后,咱们先假设BankAccount
的实例化已经知足要求了,接下来,用户但愿怎么使用BankAccount
呢?必定但愿往里面存钱,嗯,但愿BankAccount
有一个deposit函数,经过调用该函数,能够增长帐户余额。因而咱们增长下一个测试。
public function testDeposit(){ $account = new BankAccount(); $account->deposit(10); $this->assertEquals(10, $account->value()); }
帐户初始余额是0,咱们往里面存10元,其帐户余额固然应该为10。运行phpunit,测试失败,由于deposit函数尚未定义:
.PHP Fatal error: Call to undefined method BankAccount::deposit() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 11
接下来在源文件中增长deposit函数:
public function deposit($ammount) { }
再运行phpunit,得以下结果:
1) BankAccountTest::testDeposit Failed asserting that 0 matches expected 10.
这时由于咱们在deposit函数中并无操做帐户余额,余额初始值为0,deposit函数执行以后依然是0,不是用户指望的行为。咱们应该往余额上增长用户存入的数值。
为了操做余额,余额应该是BankAccount的一个成员变量。这个变量不容许外界随便更改,所以定义为私有变量。下面咱们在生产代码中加入私有变量$value
,那么value
函数应该返回$value
的值。
class BankAccount { private $value; public function value(){ return $this->value; } public function deposit($ammount) { $this->value = 10; } }
运行 phpunit,测试经过。接下来,咱们想,用户还须要什么?对,取钱。当取钱时,帐户余额要扣除这个值。若是给 deposit
函数传递负数,就至关于取钱了。
因而咱们在测试代码的testDeposit
函数中增长两行代码。
$account->deposit(-5); $this->assertEquals(5, $account->value());
再运行 phpunit,测试失败了。
1) BankAccountTest::testDeposit Failed asserting that 10 matches expected 5.
这时由于在生产代码中咱们简单地把$value
设成10的结果。改进生产代码。
public function deposit($ammount) { $this->value += $ammount; }
再运行phpunit,测试经过。
接下来,我想到,用户可能须要一个不一样的构造函数,当建立BankAccount
对象时,能够传入一个值做为帐户余额。因而咱们在testNewAccount
增长这种实例化的测试。
public function testNewAccount(){ $account1 = new BankAccount(); $this->assertEquals(0, $account1->value()); $account2 = new BankAccount(10); $this->assertEquals(10, $account2->value()); }
运行phpunit,结果为:
1) BankAccountTest::testNewAccount Failed asserting that null matches expected 10.
这时由于BankAccount
没有带参数的构造函数,所以new BankAccount(10)
会返回一个空对象,空对象的value()
函数天然返回的也是null。为了经过测试,咱们在生产代码中增长带参数的构造函数。
public function __construct($n){ $this->value = $n; }
再运行测试:
1) BankAccountTest::testNewAccount Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5 /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:5 2) BankAccountTest::testDeposit Missing argument 1 for BankAccount::__construct(), called in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 12 and defined /home/wuchen/projects/jolly-code-snippets/php/phpunit/src/BankAccount.php:5 /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php:12
两个调用new BankAccount()
的地方都报告了错误,增长了带参数的构造函数,不带参数的构造函数又不行了。从c++/java
过渡来的同窗立刻想到增长一个默认的构造函数:
public function __construct() { $this->value = 0; }
但这样是不行的,由于php不支持函数重载,因此不能有多个构造函数。
怎么办?对了,咱们能够为参数增长默认值。修改构造函数为:
public function __construct($n = 0){ $this->value = $n; }
这样调用 new BankAccount()
时,至关于传递了0给构造函数,知足了需求。
phpunit运行如下,测试经过。
这时,咱们的生产代码为:
<?php class BankAccount { private $value; // default to 0 public function __construct($n = 0){ $this->value = $n; } public function value(){ return $this->value; } public function deposit($ammount) { $this->value += $ammount; } } ?>
虽然咱们的代码并很少,可是每一步都写得颇有信心,这就是TDD的好处。即便你对php的语法不是颇有把握(好比我),也能够对本身的代码颇有信心。
用TDD的方式写程序的另外一个好处,就是编码以前不须要对单个模块进行仔细的设计,能够在写测试的时候进行设计。这样开发出来的模块既能够知足用户须要,也不会冗余。
后面将会介绍 phpunit 的更多用法。