測試驅動開發網上也談了很多了,PHP方面的文章也有一些,在百度和Google里搜,好像沒有看到幾篇談用Mock(偽裝對象)的技術的,這里寫篇文章講講。
先過一下測試驅動開發的基本理念:就是先寫測試用例(一般這個測試用例都是自動化的單元測試用例,便於快速回滾執行),然后通過逐步修復測試用例的方法補齊產品代碼,最后測試用例修復完畢后,產品也就寫完了。
從我自己的實踐中,我認為在類庫開發的時候使用測試驅動開發技術是一個很好的方案,理由如下:
能夠寫出測試用例,即說明對問題域已經有一個清晰的了解,
節省了寫文檔的時間,測試用例就是類庫調用的示例代碼了。
代碼質量有保證,因為寫類庫的過程就是修復測試用例的過程,所以測試用例修復完畢后類庫也就寫完了。
便於估時,估計類庫開發時間的問題就簡化成估計修復測試用例的時間了,相對於來說估時容易一些。
我們以編寫一個字符串轉數字的函數為例講解測試驅動開發的理念,再引入Mock技術。在開始之前,需要安裝PHPUnit和Mockery庫(本文不使用PHPUnit自帶的Mock庫):
# 安裝PHPUnit pear config-set auto_discover 1 pear install pear.phpunit.de/PHPUnit # 安裝Mock庫 sudo pear channel-discover pear.survivethedeepend.com sudo pear channel-discover hamcrest.googlecode.com/svn/pear sudo pear install --alldeps deepend/Mockery
那從測試驅動開發的理念來做的話,我們先寫測試用例 – parseinttest.php:
<? class ParseIntTest extends PHPUnit_Framework_TestCase { public function testParseIntBasic() { $v = parse_int("12345"); $this->assertEquals(12345, $v); $v = parse_int("-12345"); $this->assertEquals(-12345, $v); /* $v = parse_int("abcd"); $this->assertEquals(0, $v); $v = parse_int("0xab12"); $this->assertEquals(0xab12, $v); $v = parse_int("01b"); $this->assertEquals(1, $v); */ } } ?>
上面的代碼里,我們通過單元測試用例指定了要實現的函數parse_int的需求,即可以解析整數、帶符號的整數等,又因為時間和資源的限制,那我們去掉了對十六進制和二進制的支持。
運行下面的命令執行測試用例:
phpunit --verbose parseinttest
因為這個時間沒有任何代碼,所以得到期望的錯誤 – PHPUnit告訴我們找不到parse_int這個函數:
PHPUnit 3.6.12 by Sebastian Bergmann.
PHP Fatal error: Call to undefined function parse_int() in /var/www/pmdemo/parseinttest2.php on line 6
那我們先建一個空的parse_int函數 – parseint.php:
<?
function parse_int($str) {
return 0;
}
?>
上面的代碼里我們先不實現函數parse_int的任何邏輯,再運行測試用例,得到:
shiyimin@ubuntu:/var/www/pmdemo$ phpunit --verbose parseinttest
PHPUnit 3.6.12 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) ParseIntTest::testParseIntBasic
Failed asserting that 0 matches expected 12345.
/var/www/pmdemo/parseinttest.php:7
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.
從上面紅色高亮顯示出來的錯誤消息,我們知道是測試用例里的第一個判斷沒有通過,即下面的判斷語句沒有執行成功:
$this->assertEquals(12345, $v);
這是因為我們的parse_int函數總是返回0這個值,發現了這個錯誤,我們來補齊這個邏輯以修復測試用例:
parseint.php
<? function parse_int($str) { $result = 0; $i = 0; for ( $i = 0; $i < strlen($str); ++$i ) { $result *= 10; $result += $str[$i] - '0'; } return $result; } ?>
再次運行測試用例:
shiyimin@ubuntu:/var/www/pmdemo$ phpunit --verbose parseinttest
PHPUnit 3.6.12 by Sebastian Bergmann.
F
Time: 0 seconds, Memory: 2.75Mb
There was 1 failure:
1) ParseIntTest::testParseIntBasic
Failed asserting that 12345 matches expected -12345.
/var/www/pmdemo/parseinttest.php:10
FAILURES!
Tests: 1, Assertions: 2, Failures: 1.
從上面的結果可以看出,第一個測試用例已經修復了,現在是在處理帶符號的字符串時,發生了問題,繼續修復代碼:
parseint.php
<? function parse_int($str) { $result = 0; $i = 0; $neg = 1; if ( $str[0] == '-' ) { $neg = -1; } for ( $i = 0; $i < strlen($str); ++$i ) { $result *= 10; $result += $str[$i] - '0'; } return $result * $neg; } ?>
再運行測試用例:
shiyimin@ubuntu:/var/www/pmdemo$ phpunit --verbose parseinttest
PHPUnit 3.6.12 by Sebastian Bergmann.
.
Time: 0 seconds, Memory: 2.75Mb
OK (1 test, 2 assertions)
好了,這次所有測試用例都通過了,那我們的產品代碼的實現也告一段落了,當然文章為了講解方便,上面的代碼並不是一個完整的實現,例如它就無法處理字符串“+12345”的情形。
下篇文章講解如何在PHPUnit里應用Mock技術。