PHP測試驅動開發介紹

日期:2008-07-09  作者:喜騰小二  來源:PHPChina


摘要

本文嚮妳介紹測試驅動開發的概念,並用一個簡單的範例項目來做示範。

介紹

開發PHP產品有很多不同的方法。我們大多數傾嚮於從一個簡單的指令檔開始,逐步嚮前推進。 或許我們可以預先列出我們的指令檔,但是我們往往是停留在開發階段,在需要測試的時候不會真正的去開始測試。基本上,我們是先開發後測試。

但是這樣做或許不是最好的辦法,可能會在今後帶來問題。這就是為什麼一些開發者提倡一種不同的開發方式,叫做測試驅動開發(TDD)的原因- 就是先測試後開發。

妳可能會疑惑這樣該怎麼做,而這正是本文將討論的。我將帶領妳們透過一個真實的簡單項目去示範TDD如何工作。本文和範例項目都基於Noel Darlow(“McGruff”)在論壇中嚮另一個論壇成員演示TDD如何工作的討論 

我們的範例項目是一個Biter類,它透過使用規則運算式可以“咬掉”字串中的片段,就象這樣:

bite ('/pattern/');?>

我們的類也將修改原始的字串,把比對的部分去除(所以我們叫它“吞噬者”)。

讓我們從設定測試框架開始。

設定測試框架

由於我們從測試出發,我們需要有一些測試框架。我將使用SimpleTest 框架,僅僅因為我最熟悉它。

下載一份SimpleTest的拷貝,把它安裝在妳本機或者妳的伺服器上。然後建立一個叫做"test_biter.php"的新檔案,裡麵寫下麵的程式碼:


require_once'simpletest/unit_tester.php';
require_once'simpletest/reporter.php';

class BiterTestCase extends UnitTestCase {
        function testSetup (){
                $this->assertTrue(false);       
        }
}

$test = new BiterTestCase('TDD Biter Test');

$test->run(new HtmlReporter());
?>

讓我們分析一下這個例子。首先我們包含了一些SimpleTest框架的檔案(妳要確認一下路徑是否和妳的一樣)。接着我們建立了一個叫做 BiterTestCase的新類,它將用來測試我們的Biter類。象妳看到的一樣,BiterTestClase類繼承於UnitTestCase 類,這個意味着BiterTestClass是我們第一個真正意義上的測試用例。

BiterTestClass類隻有一個方法調用叫做'testSetup'。任何以“test”開頭的方法都會被SimpleTest框架自動執行,因此它們應該是被用來測試項目中的某個部分。在上麵的例子中,我們透過調用assertTrue()方法來確認框架被正確設定。

例子中的後麵兩行是建立一個測試用例的實例,然後執行所有測試。如果所有設定都正確的話,妳會得到下麵的輸出:

setup_thumb.jpg

現在我們已經設定好了測試框架並正常執行。讓我們開始我們的Biter類。

第一個測試

在我們測試Biter類的任何功能之前,我們必須確認這個類存在。我們當然也是寫一個測試來做這件事:


require_once
'simpletest/unit_tester.php';
require_once'simpletest/reporter.php';

class BiterTestCase extends UnitTestCase {
        function testClassExists(){
                $this->assertTrue(class_exists('Biter'), 'Biter class exists')
        }
}

$test = new BiterTestCase('TDD Biter Test');
$test->run(new HtmlReporter());

?>

現在執行上麵的測試。不要建立類或者包含其他內容;先執行測試。由於'Biter' 類不存在,妳會看到下麵的輸出:

class_exists_fail_thumb.jpg

如妳所見,我們的測試指出'Biter'類不存在。TDD中,妳總是從一個失敗的測試開始工作

現在我們必須讓測試能夠透過,做到這點需要建立Biter類。建立一個叫'biter.php'的新檔案,寫入如下程式碼:

Class Biter {

}

?>

然後把Biter類包含到我們的測試檔案,加入如下行

require_once'biter.php';

現在再執行測試。這次它們會透過了,輸出如下:

class_exists_success_thumb.jpg

在建立Biter類的時候,我們走出了TDD的第一步。首先我們建立測試,然後再做實際的開發。

在目前我們的Biter類還沒有任何功能,下麵讓我們開始我們的Biter類的第一部分:“開始吞噬”。

Biter返回正確的比對嗎?

我們希望第一步明確的是當我們調用bite()方法的時候Biter類將返回正確的比對,因此,讓我們就此寫一個測試:

function testBiteReturnsAPatternMatch(){
$biter =& new Biter;
        $haystack = 'foobar';
               
        $this->assertEqual(
                        $biter->bite('/foo/', $haystack),
                        'foo',
                        'Biter returns correct match');
}

這個測試確認biter返回正確的比對。它使用了assertEqual()方法,從名字我們就能知道這個方法的意思。把測試增加到我們的測試集,然後再執行測試。它會出錯甚至返回一個緻命錯誤,因為我們還沒提供bite()方法。妳會看到下麵的輸出:

returns_match_fail_thumb.jpg

就象我們在前一個測試裡麵做的一樣,讓我們設法使這個測試透過。為了達到這點,我們必須提供bite()方法,由於我們的測試定義了bite()方法需要如何工作,我們隻需要開發它,不再需要思考它應該怎麼樣。這個是TDD的另一個核心概念:測試定義了程式碼行為。

下麵的bite()方法可以使得測試透過:

function bite($needle, $haystack){
        if(preg_match($needle, $haystack, $match)){
                return$match['0'];
        }
}

在妳的Biter類增加bite()方法,然後再次執行測試。現在應該透過了,產生了下麵的畫麵:

returns_match_success_thumb.jpg

讓我們重溫一下剛才我們所做的。我們沒有立刻建立bite()方法,我們先為此建立了一個測試,在那之後我們再提供bite()方法。這個就是TDD提倡的:先測試,後開發

移除比對的部分

我們現在還沒有完全完成,因為我們的Biter類有第二個需求:它需要把比對的部分從原始的字串中移除。為此讓我們再寫一個測試:

function testPatternMatchIsRemovedFromHaystack(){
        $biter =& new Biter;
        $haystack = 'foobar';
        $biter->bite('/foo/', $haystack);
        $this->assertEqual($haystack, 'bar', 'Removed matched part [%s]');
}

我們還是使用assertEqual()方法去斷言兩個變數完全相同,隻是這次我們是去檢查比對部分是否已經被移除。

執行上麵的測試,將得到下麵的輸出:

removed_fail_thumb.jpg

現在我們必須使得這個測試透過。這個做起來很簡單,不過假設我們不寫這個測試,而是透過修改其他測試來讓這個測試透過,這樣可能可以少些測試程式碼。怎麼樣,聽起來不錯?其實是錯誤的!

永遠不能為了讓某一個測試更容易透過而去修改其他的測試!

哪些其他的測試可能是為了特定需求的結果建立的。妳實際執行的指令檔可能會依賴於那些測試所透過的特定函式。如果妳開始修改其他的測試,妳就會失去TDD的所有優勢。這就是為什麼妳永遠不能為了更容易透過某個測試而去修改其他測試的原因。

讓我們回到我們的Biter類。我們如何能讓這個測試透過?我們必須修改傳遞進來的字串,就好像我們處理透過引用的方式傳遞的參數。修改過的bite()方法看上去象下麵這樣:

function bite($needle, &$haystack){
        if(preg_match($needle, $haystack, $match)){
                $haystack = str_replace($match['0'], '', $haystack);
                return$match['0'];
        }
}

把它放入妳的Biter類,然後執行測試。妳看到什麼?全部都是綠色!

removed_success_thumb.jpg

現在我們滿足了Biter類的所有需求,我們可以開始做我們的指令檔裡麵的其他類,開發起來和我們開發Biter類一樣。同時妳還能得到另一個好處,就是以後妳可以再執行Biter類的測試以便確認它的功能依然正確(這將有效的避免開發過程中偶然的錯誤修改)。

結論

本文中我演示了測試驅動開發如何工作。我們隻是構造了一個真實的簡單的類,它非常容易去使用TDD方法,不過做為介紹還是不錯的選擇。

值得注意的是TDD需要主要焦點的轉移,因此它很難推行,它使得開發過程完全不同以往。我可以給出的最好建議是堅持嘗試TDD,最終妳會迷戀它。一旦習慣了,妳再也不會用其他的方法。

如果妳想瞭解文中的範例項目或者想得到更多的範例,可以看看SitePoint 論壇的相關討論。非常感謝Noel Darlow使得本文得以呈現,並且提供了本文的大部分範例和資訊。

如果妳對本文有任何問題和建議,歡迎在PHPit論壇與我們討論。

<<<返回技術中心

技術文章

站內新聞

我要啦免费统计