使用 PHPUnit 進行 PHP 的單元測試
PHPUnit是一個用PHP編程語言開發的開源軟件,是一個單元測試框架。PHPUnit由Sebastian Bergmann創建,源于Kent Beck的SUnit,是xUnit家族的框架之一。本文將探索PHPUnit,特別介紹自動化單元測試的基本用法。你需要掌握PHP編程語言的基本知識才能繼續。
作者:Kendrick Curtis,Stainless Software, http://www.stainless-software.com/
介紹
單元測試是對單獨的代碼對象進行測試的過程,比如對函數、類、方法進行測試。單元測試可以使用任意一段已經寫好的測試代碼,也可以使用一些已經存在的測試框架,比如JUnit、PHPUnit或者Cantata++,單元測試框架提供了一系列共同、有用的功能來幫助人們編寫自動化的檢測單元,例如檢查一個實際的值是否符合我們期望的值的斷言。單元測試框架經常會包含每個測試的報告,以及給出你已經覆蓋到的代碼覆蓋率。
安裝
PHPUnit 通常以 PEAR 包,Composer bundle 或是 PHAR 文件形式存在。如果你要安裝它,你需要先安裝 PHP Code Coverage 依賴。在 PEAR 中,你需要天價 phpunit.de 頻道,并通過命令行安裝兩個包:
(注意,在輸入時,默認的 XAMPP 的 PEAR 安裝已經被破壞:你需要在嘗試上面代碼之前先安裝 PEAR PHAR)。
測試一個簡單的類
試試只有單一方法的簡單類:
class TruthTeller { public function() tellTruth { return true; } }
是的,現在 tellTruth 方法總是返回 TRUE,那么我們應改怎么通過單元測試確保今后它的返回值不變?
使用PHPUnit,每組測試是PHPUnit_Framework_TestCase類的一個擴展類,它提供了常用的功能,如判斷。下面是一個對上述tellTruth方法的一個基本測試:
require_once 'PHPUnit/Autoload.php'; require_once 'TruthTeller.class.php'; class TruthTester extends PHPUnit_Framework_TestCase { function testTruthTeller() { $tt = new TruthTeller(); $this->assertTrue($tt->tellTruth()); } }
請注意,您需要包括PHPUnit的自動加載器和“被測對象”,在這種情況下的TruthTeller類文件。
我們用剩余的代碼要做的就是判斷,如果tellTruth方法被調用時,它將返回true。這些判斷是PHPUnit的核心 - 它們將決定一個測試是通過還是失敗。
如果你啟動了命令行提示,切換到你的測試所在目錄,運行 phpunit TruthTester (參數是你的測試文件名,去除 .php 擴展名),PHPUnit 將會運行文件中指定的所有它能找到的測試(測試將是名字以 test 開頭的所有方法)。
如果你回到 TruthTeller 類,并將其方法的返回值改為 FALSE,你講看到類似下面的信息:
這就是單元測試的核心——編寫斷言并判斷是否通過。當先前編寫并測試通過的代碼開始無法通過時,你就知道有更改的代碼對現有代碼起了負面影響。
更復雜的測試
在現實中,你肯定需要處理比上一個更復雜的情況。比如一個常見的測試是檢查下面的outputArray方法是否返回了一個特定數據結構的數組。
class ArrayTeller { public function outputArray() { return array(1,2,3); } }
對此方法的一個簡單測試可以這樣寫:
class ArrayTester extends PHPUnit_Framework_TestCase { function testArrayTeller() { $at = new ArrayTeller(); $result = $at->outputArray(1); $this->assertInternalType("array", $result); $this->assertCount(3, $result); $this->assertEquals(1, $result[0]); $this->assertEquals(3, $result[2]); } }
如你所見,使用PHPUnit進行單元測試時可以在每一行進行多樣化的檢查:可以檢查ArrayTeller返回的是否是一個數組,而非任何其他數據類型;可以檢查數組的長度;可以檢查數組中的單個值。除這些外,還有其它一些功能的斷言,比如如果你需要更復雜的判斷,假設要知道一個返回值是否處于兩個整數的區間內,只要你能用一個IF語句的結果來表述,你就可以用斷言assertTrue來測試結果。可點擊隨后鏈接來訪問PHPUnit官網文檔中的所有可用斷言列表,。
測試代碼路徑
單元測試片面的講就是編寫覆蓋被測方法所有預期行為的測試,最好基于規范文檔,不過如果你在編寫覆蓋現有代碼的單元測試,將其視為白盒測試的一種形式更有用。如果你知道一個如下的簡單切換方法:
class Switcher { public function aOrB($switch, $a, $b) { if ($switch == TRUE) { return $a; } else { return $b; } } }
… 你就知道需要編寫兩個測試,分別針對一種情形。但你開始質疑你是如何知道這些的——如果以后方法變為 True 返回 $a,False 返回 $b,其它情形拋出一個異常,理想情況是規范文檔中的某處有提及。無論如何,上面方法的測試如下:
class SwitcherTester extends PHPUnit_Framework_TestCase { function testSwitchTrue() { $switcher = new Switcher(); $result = $switcher->aOrB(TRUE, 1, 2); $this->assertEquals(1, $result); } function testSwitchFalse() { $switcher = new Switcher(); $result = $switcher->aOrB(FALSE, 1, 2); $this->assertEquals(2, $result); } }
運行兩個測試同在命令行運行 phpunit SwitcherTester 一樣簡單。
使用 setUp,簡化多個測試
當你的測試需要覆蓋越來越多的輸入組合及數據設置時,使用函數: setUp 將會非常有幫助。setUp 是 PHPUnit_Framework_TestCase 類中你可以覆寫以在類中所有及每個測試運行前運行的代碼。(注意,還有一個簡單的方法,tearDown,它會在所有測試結束后立即運行——這對關閉 socket 及文件指針很有幫助)
下面是如何精簡代碼的一個簡單的例子。嘗試一個依賴一些對象數據何輸入的方法。
class DataTeller { private $data; public function __construct($data) { $this->data = $data; } public function outputData($switch) { if ($switch == TRUE) { if (!empty($this->data)) return $this->data; else return FALSE; } else { return "switch off"; } } }
如果你繼續之前幼稚的方法,我們需要編寫三個測試,并實例化三個 DataTeller 對象,每個測試一次。然而,通過 setUp,我們可以講對 DataTellers對象的創建外包,至少是3個中的兩個。只有最后一個測試需要新的 DataTeller 被創建。
class DataTellerTester extends PHPUnit_Framework_TestCase { private $dt; protected $data = "valid data"; function setUp() { $this->dt = new DataTeller($this->data); } function testOutputArraySwitchOff() { $this->assertEquals("switch off", $this->dt->outputData(FALSE)); } function testOutputArraySwitchOn() { $this->assertEquals($this->data, $this->dt->outputData(TRUE)); } function testOutputArrayEmptySwitchOn() { $new_dt = new DataTeller(""); $this->assertEquals(FALSE, $new_dt->outputData(TRUE)); } }
結論
PHPUnit使用斷言來告訴你你所測試的代碼是否如你預期那樣工作。學到這里,你現在應該已經可以寫一些簡單的測試來覆蓋一些功能相對比較獨立的類了。但當要測試一些互相有交互操作的類時,就要面對真正的挑戰了。為此,請收聽下一次講解,學習如何為靜態類寫測試,以及如何使用 mock(模擬對象)和 stubs(存根,樁點)來孤立你要測試的對象與其所在環境中其他代碼的聯系。
延伸閱讀
- PEAR站點上的PHPUnit 教程 </p>
關于作者
Kendrick Curtis(肯德里克.柯蒂斯)是一個有十年經驗的Web開發者。他是 Stainless Software(不銹鋼軟件)公司的創始人,提供特約的網頁設計,開發,測試和內容創作。有關更多信息可參閱其公司網站: http://www.stainless-software.com/
相關內容: