你是否在程序開發的過程中遇到以下的情況:當你花了很長的時間開發一個應用後,你認爲應該是大功告成了,可惜在調試的時候,老是不斷的發現bug,而且最可怕的是,這些bug是重複出現的,你可能發現這些bug之間會有關聯,但卻老是找不到問題的所在。
當你遇到以上這些令你沮喪的情況時,你一定會想能有什麼更好的辦法去解決呢?辦法當然是有的!這就是使用單元測試。單元測試不但可以在一定程度上解決上述頭疼的問題,而且能讓代碼變的容易維護,還可以能讓你更多地對代碼進行重構。
一旦你編寫好單元測試用例,當你需要修改你的代碼時,你要做的事情就是重新運行你的單元測試用例並觀察這些單元測試用例能否通過,如果通過了的話,證明代碼是沒問題的。
人們往往會說:既然單元測試這麼好,爲什麼那麼多人還是不大願意去寫單元測試呢?有以下幾種理解上的誤曲:
1、認爲編寫單元測試太浪費時間。雖然目前很多IDE工具都爲編寫單元測試建立好了框架,但還是要開發者編寫一些單元測試的代碼的。就象很多開發中的最佳實踐一樣,用正確的方法去做正確的事情會爲開發節省大量的時間。每當新增加新功能時,你可能通過訪問你的網頁到處去點擊手動測試,而運行建立好的單元測試用例其速度其實比通過手工去測試的速度更快。
2、認爲既然代碼能運行了,不需要再編寫單元測試。但假設團隊中有新的成員,如果沒有良好的單元測試用例,新成員很有可能隨意地去編碼而不考慮各種後果。如果有編寫良好的單元測試,在程序運行時進行各種測試,則能最大程度避免bug的產生。
3、認爲編寫單元測試代碼枯燥無味。程序員的天性是解決問題,而很多程序員認爲在緊張的編碼工作時,還要編寫單元測試代碼,會很枯燥。但要知道的是,如果能通過編寫單元測試在很早的階段就能儘可能發現代碼中多的錯誤的話,那麼既節省時間減少了出錯,何樂而不爲?
開始動手安裝phpunit
本文中將通過介紹php中的單元測試利器phpunit(http://phpunit.de/),並通過實際例子來講解如何在實際工作中運用phpunit。首先安裝phpunit的方法可以通過php下的pear去安裝:
pear channel-discover components.ez.no
pear channel-discover pear.symfony-project.com
pear install phpunit/PHPUnit
如果你想通過手動方式去安裝,可以參考phpunit的手冊去安裝(http://www.phpunit.de/manual/3.0/en/installation.html)。
編寫第一個單元測試用例
下面我們開始編寫第一個單元測試用例。在編寫測試用例時,要遵守如下的phpunit的規則:
1 一般地,在測試用例中,可以擴展PHPUnit_Framework_TestCase類,這樣就可以使用象setUp(),tearDown()等方法了。
2 測試用例的名字最好是使用約定俗成的格式,即在被測試類的後面加上”Test”,比如要測試的類爲RemoteConnect,則測試用例的命名爲RemoteConnectTest。
3 在一個測試用例中的所有的測試方法,在命名時都應該以test+測試方法名去命名,如testDoesLikeWaffles(),要注意的是該方法必須是聲明爲public類型的。當然可以在你的測試用例中包含private的方法,但它們不能被phpunit所調用。
4 測試方法中是不能接收參數的。
下面首先舉個簡單的例子,代碼如下:
class RemoteConnect
{
public function connectToServer($serverName=null)
{
if($serverName==null){
throw new Exception(“That's not a server name!”);
}
$fp = fsockopen($serverName,80);
return ($fp) ? true : false;
}
public function returnSampleObject()
{
return $this;
}
}
?>
上面的代碼其實是實現連接到一個指定的服務器的功能,那麼我們可以編寫測試代碼如下:
require_once('RemoteConnect.php');
class RemoteConnectTest extends PHPUnit_Framework_TestCase
{
public function setUp(){ }
public function tearDown(){ }
public function testConnectionIsValid()
{
// test to ensure that the object from an fsockopen is valid
$connObj = new RemoteConnect();
$serverName = 'www.google.com';
$this->assertTrue($connObj->connectToServer($serverName) !== false);
}
}
?>
在上面的代碼中,由於繼承了PHPUnit_Framework_TestCase類,因此在setUp和tearDown方法中,不需要編寫任何代碼。SetUp方法是在每個測試用例運行前進行一些初始化的工作,而tearDown則在每個測試用例運行後進行一些比如資源的釋放等工作。在測試方法中,通過使用phpunit的斷言assertTrue去判斷所返回的布爾值是否爲真,這裏是通過調用RemoteConnect.php中的connectToServe方法去判斷能否連接上服務器。
接下來我們運行這個單元測試,在命令行下輸入代碼:
phpunit /path/to/tests/RemoteConnectTest.php即可,可以看到測試順利通過的話,會輸出以下結果:
.
Time: 1 second
Tests: 1, Assertions: 1, Failures 0
可以看到,上面是通過了測試。默認情況下,phpunit是會運行測試用例中的所有測試方法的。下面再介紹下phpunit中相關的幾個斷言:
AssertEquals 判斷輸出是否和預期的相等
AssertGreaterThan 斷言結果是否大於某個值,同樣的也有LessThan(小於),GreaterThanOrEqual(大於等於),
LessThanOrEqual(小於等於).
AssertContains 判斷輸入是否包含指定的值
AssertType 判斷是否屬於指定類型
AssertNull 判斷是否爲空值
AssertFileExists 判斷文件是否存在
AssertRegExp 根據正則表達式判斷
舉個例子來說明下比如AssertType的使用,依然以上面的例子來說,可以用AssertType去判斷returnSampleObject返回的對象實例是否爲remoteConnect,代碼如下:
function testIsRightObject() {
$connObj = new RemoteConnect();
$returnedObject = $connObj->returnSampleObject();
$this->assertType('remoteConnect', $returnedObject);
}
?>
目前PHP框架對單元測試的支持
目前很多優秀的php框架(如Zend Framework,Symfony等),都提供了對單元測試很好的支持。以Zend Framework爲例,說明下其中是如何運行單元測試的。
class CommentControllerTest extends Zend_Test_PHPUnit_ControllerTestCase
{
public function setUp()
{
parent::setUp();
}
public function tearDown()
{
parent::tearDown();
}
public function appBootstrap()
<img alt="\" align="top" src="http://www.php100.com/cms/uploads/allimg/110216/0Z3551064-0.gif" numpage"="" style="padding: 0px; margin: 0px; border: 0px;">