首頁  >  文章  >  後端開發  >  用phpUnit入門TDD

用phpUnit入門TDD

WBOY
WBOY原創
2016-08-08 09:29:491420瀏覽

用phpunit實戰TDD系列

從一個銀行帳戶開始

假設你已經 安裝了phpunit.

我們從一個簡單的銀行帳戶的例子開始了解TDD(Test-Driven-Development)的想法。

在工程目錄下建立兩個目錄, srctest,在src下建立檔案 BankAccount.php,在test目錄下建立檔案BankAccountTest。

依照TDD的思想,我們先寫測試,再寫生產程式碼,所以

BankAccount.php留空,我們先寫BankAccountTest.php

<code><?php
class BankAccountTest extends PHPUnit_Framework_TestCase
{
}
?></code>
現在我們運行一下,看看結果。運行phpunit的命令列如下:

<code>phpunit --bootstrap src/BankAccount.php test/BankAccountTest.php</code>

--bootstrap src/BankAccount.php是說在執行測試程式碼之前先載入 src/BankAccount.php,要執行的測試程式碼是test/BankAccountTest.php

如果不指定特定的測試文件,只給出目錄,phpunit則會運行目錄下所有文件名匹配

*Test.php 的文件。因為test目錄下只有BankAccountTest.php一個文件,所以執行

<code>phpunit --bootstrap src/BankAccount.php test</code>
會得到一樣的結果。

<code>There was 1 failure:

1) Warning
No tests found in class "BankAccountTest".

FAILURES!
Tests: 1, Assertions: 0, Failures: 1.</code>
一個警告錯誤,因為沒有任何測試。

帳戶實例化

下面我們新增一個測試。注意,TDD是一種設計方法,可以幫助你自底向上設計一個模組的功能。我們寫測試的時候,要從使用者的角度出發。如果使用者使用我們的

BankAccount類,他首先做什麼事呢?一定是新建一個BankAccount的實例。那我們第一個測試就是對於 實例化 的測試。

<code>public function testNewAccount(){
    $account1 = new BankAccount();
}</code>
運行phpunit,意料之中地失敗。

<code>PHP Fatal error:  Class 'BankAccount' not found in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 5</code>
沒有發現

BankAccount類別的定義,下面我們就要寫生產代碼。使測試通過。在src/BankAccount.php(後面稱之為原始檔)中輸入以下內容:

<code><?php
class BankAccount {
}
?></code>
運行phpunit,測試通過。

<code>OK (1 test, 0 assertions)</code>
接下來,我們要增加測試,使得測試失敗。如果新建一個帳戶,帳戶的餘額應該是0。於是我們加入了一個

assert語句:

<code>public function testNewAccount(){
    $account1 = new BankAccount();
    $this->assertEquals(0, $account1->value());
}</code>
注意

value()BankAccount的一個成員函數,當然這個函數還沒有定義,作為用戶我們希望BankAccount提供這個函數。

運行phpunit,結果如下:

<code>PHP Fatal error:  Call to undefined method BankAccount::value() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 6</code>
結果告訴我們

BankAccount並沒有value()這個成員函數。新增生產代碼:

<code>class BankAccount {
    public function value(){
        return 0;
    }
}</code>
為什麼要讓

value()直接回傳0,因為測試程式碼中希望value()回傳0。 TDD的原則就是不寫多餘的生產程式碼,剛好讓測試通過即可。

帳戶的存取

運行phpunit通過後,我們先假設

BankAccount的實例化已經滿足要求了,接下來,用戶希望怎麼使用BankAccount呢?一定希望往裡面存錢,嗯,希望BankAccount有一個deposit函數,透過呼叫該函數,可以增加帳戶餘額。於是我們增加下一個測試。

<code>public function testDeposit(){
    $account = new BankAccount();
    $account->deposit(10);
    $this->assertEquals(10, $account->value());
}</code>
帳戶初始餘額是0,我們往裡面存10元,其帳戶餘額當然應該為10。運行phpunit,測試失敗,因為deposit函數還沒有定義:

<code>.PHP Fatal error:  Call to undefined method BankAccount::deposit() in /home/wuchen/projects/jolly-code-snippets/php/phpunit/test/BankAccountTest.php on line 11</code>
接下來在原始檔中增加deposit函數:

<code>public function deposit($ammount) {
}</code>
再運行phpunit,得如下結果:

<code>1) BankAccountTest::testDeposit
Failed asserting that 0 matches expected 10.</code>
這時因為我們在deposit函數中並沒有操作帳戶餘額,餘額初始值為0,deposit函數執行之後依然是0,不是使用者期望的行為。我們應該往餘額上增加用戶存入的數值。

為了操作餘額,餘額應該是BankAccount的一個成員變數。這個變數不允許外界隨便更改,因此定義為私有變數。下面我們在生產程式碼中加入私有變數

$value,那麼value函數應該會回傳$value的值。

<code>class BankAccount {
    private $value;
    
    public function value(){
        return $this->value;
    }

    public function deposit($ammount) {
        $this->value = 10;
    }
}</code>
運行 phpunit,測試通過。接下來,我們想,用戶還需要什麼?對,取錢。取錢時,帳戶餘額要扣除這個值。如果給

deposit函數傳遞負數,就等於取錢了。 所以我們在測試程式碼的
testDeposit函數中增加兩行程式碼。

<code>$account->deposit(-5);
$this->assertEquals(5, $account->value());</code>
再運行 phpunit,測試失敗了。

<code>1) BankAccountTest::testDeposit
Failed asserting that 10 matches expected 5.</code>
這時因為在生產代碼中我們簡單地把

$value設為10的結果。改進生產代碼。

<code>public function deposit($ammount) {
    $this->value += $ammount;
}</code>
再運行phpunit,測試通過。

新的建構子

接下來,我想到,使用者可能需要一個不同的建構函數,當創建

BankAccount物件時,可以傳入一個值作為帳戶餘額。於是我們在testNewAccount增加這種實例化的測試。

<code>public function testNewAccount(){
    $account1 = new BankAccount();
    $this->assertEquals(0, $account1->value());
    $account2 = new BankAccount(10);
    $this->assertEquals(10, $account2->value());
}</code>
運行phpunit,結果為:

<code>1) BankAccountTest::testNewAccount
Failed asserting that null matches expected 10.</code>

这时因为BankAccount没有带参数的构造函数,因此new BankAccount(10)会返回一个空对象,空对象的value()函数自然返回的也是null。为了通过测试,我们在生产代码中增加带参数的构造函数。

<code>public function __construct($n){
    $this->value = $n;
}</code>

再运行测试:

<code>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</code>

两个调用new BankAccount()的地方都报告了错误,增加了带参数的构造函数,不带参数的构造函数又不行了。从c++/java过渡来的同学马上想到增加一个默认的构造函数:

<code>public function __construct() {
    $this->value = 0;
}</code>

但这样是不行的,因为php不支持函数重载,所以不能有多个构造函数。

怎么办?对了,我们可以为参数增加默认值。修改构造函数为:

<code>public function __construct($n = 0){
    $this->value = $n;
}</code>

这样调用 new BankAccount()时,相当于传递了0给构造函数,满足了需求。
phpunit运行以下,测试通过。

这时,我们的生产代码为:

<code><?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;
    }
}
?></code>

总结

虽然我们的代码并不多,但是每一步都写得很有信心,这就是TDD的好处。即使你对php的语法不是很有把握(比如我),也可以对自己的代码很有信心。

用TDD的方式写程序的另一个好处,就是编码之前不需要对单个模块进行仔细的设计,可以在写测试的时候进行设计。这样开发出来的模块既可以满足用户需要,也不会冗余。

后面将会介绍 phpunit 的更多用法。

以上就介绍了用phpUnit入门TDD,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn