用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 的更多用法。