用phpUnit入門TDD

用phpunit實戰TDD系列php

從一個銀行帳戶開始

假設你已經 安裝了phpunit.html

咱們從一個簡單的銀行帳戶的例子開始瞭解TDD(Test-Driven-Development)的思想。java

在工程目錄下創建兩個目錄, srctest,在src下創建文件 BankAccount.php,在test目錄下創建文件BankAccountTest.phpc++

按照TDD的思想,咱們先寫測試,再寫生產代碼,所以BankAccount.php留空,咱們先寫BankAccountTest.phpbootstrap

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

相關文章
相關標籤/搜索