PHPUnit 學習實例代碼
PHPUnit 基本用法+實例詳解
上一篇文章介紹了 PHPUnit 在 windows 的安裝和配置。今天我們來介紹一下 PHPUnit 如何編寫一些基本的測試用例。我也是在最近纔開始慢慢使用的 PHPUnit , 用不足之處,歡迎指正。
一、編寫規範
測試類一般以***Test 命名;
該類必須繼承 PHPUnit_Framework_TestCase 類;
類裏的測試用例方法一般以test開頭,當然也可以通過@test註釋來定義一個名字不爲test開頭的方法爲測試方法;
測試方法中需要使用斷言方法來斷言實際傳入的參數與期望的參數是否一致來達到測試的目的;
二、測試用例
基本的demo
定義一個類 DemoTest 並保存到 DemoTest.php 文件中<?php /** * phpunit test demo * @author [email protected] */ class DemoTest extends PHPUnit_Framework_TestCase { /** * test */ public function testPushAndPop() { $stack = array (); //斷言 $stack 的長度爲0 $this->assertEquals(0, count($stack)); array_push($stack, 'foo'); //斷言 $stack 的長度爲 1 $this->assertEquals(1, count($stack)); //斷言 $stack 的最後一個值爲foo $this->assertEquals('foo', $stack[count($stack)-1]); //斷言 $stack 出棧的值爲 foo $this->assertEquals('foo', array_pop($stack)); //斷言 $stack 的長度爲 0 $this->assertEquals(0, count($stack)); } /** * 定義test標籤來聲明是測試方法 * @test */ public function indexEquals() { $stack = array (0,1,2,3,4); //斷言 $stack 索引 0 的值爲2 $this->assertEquals(2, $stack[0]); } }
上面的代碼中定義了兩種測試用例的方法,一種是開頭爲test, 一種是定義@test標籤;兩種都可以。
然後運行測試這個類;打開命令窗口,進入該代碼保存的文件目錄輸入:phpunit DemoTest.php
運行結果爲:Time:825 ms, Memory: 8.25Mb There was 1 failure: 1) DemoTest::indexEquals Failed asserting that 0 matches expected 2. D:\server\apache\htdocs\my_php_code\phpunit\stack\DemoTest.php:32 FAILURES! Tests: 2, Assertions: 6, Failures: 1.
解釋一下:最上面的是一些耗時,內存消耗的多少。往下看測試結果說有一個錯誤,也就是測試未通過,在文件的32行。32行的意思是斷言這個數組中索引爲0的值爲2,顯然不是,這裏我故意寫錯,所以測試失敗。如果改爲0則會顯示OK;最後是顯示2個測試用例,6個斷言,其中一個失敗。
方法依賴關係
在測試類中,測試用例方法可以有依賴關係。通過依賴關係可以傳一些參數到方法中;因爲默認的測試用例方法是不能有參數的。
定義一個FunTest 類保存到文件中<?php /** * 測試方法依賴關係 * @author [email protected] */ class FuncTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $stack = array (); $this->assertEmpty($stack); return $stack; } /** * depends 方法用來表示依賴的方法名 * @depends testEmpty * @param array $stack * @return array */ public function testPush(array $stack) { array_push($stack, 'foo'); $this->assertEquals('foo', $stack[count($stack)-1]); return $stack; } /** * @depends testPush * @param array $stack */ public function testPop(array $stack) { $this->assertEquals('foo', array_pop($stack)); $this->assertEmpty($stack); } }
標籤@depends是表示依賴關係,上面的代碼中testPop()依賴testPush(),testPush()依賴testEmpty(), 所以當testEmpty()測試通過後返回的變量可以作爲testPush(array $stack)的參數。同理,testPop()也是一樣。
運行結果如下:Time: 726 ms, Memory: 8.25Mb OK (3 tests, 4 assertions)
測試通過
測試非依賴關係的方法傳入參數
如果非依賴關係的方法,默認是不能有參數的,這個時候怎麼樣才能傳參,PHPUnit 提供給了一個標籤,@dataProvider
定義一個DataTest類保存到文件中<?php /** * 測試非依賴關係的方法傳入參數 * 方法提供者 * @author [email protected] */ class DataTest extends PHPUnit_Framework_TestCase { /** * dataProvider 標籤用來提供數據的方法名 * @dataProvider add_provider */ public function testAdd($a, $b, $c) { //斷言 a + b = c $this->assertEquals($c, $a + $b); } /** * 數據提供者的方法 * 格式: * return array( * array(參數1,參數2,參數3,參數4,參數N), * array(參數1,參數2,參數3,參數4,參數N), * ); */ public function add_provider() { return array ( array (0, 0, 0), array (0, 1, 1), array (1, 0, 1), array (1, 1, 2), ); } }
看上面代碼應該就能明白。直接運行phpunit DataTest.php
運行結果如下:Time: 379 ms, Memory: 8.25Mb OK (3 tests, 4 assertions)
數據提供者方法和依賴關係的限制
這個聽起來有點繞口,意思是如果方法依賴和方法提供者同時使用的話,是有限制的。說半天我估計還是一塌糊塗,不解釋,直接看代碼
定義一個 Data2Test 類保存到文件<?php /** * 數據提供者方法和依賴關係的限制 * * 當一個測試方法依賴於另外一個使用data providers測試方法時, * 這個測試方法將會在它依賴的方法至少測試成功一次後運行, * 同時使用data providers的測試方法的執行的結果不能傳入一個依賴它的測試方法中 * @author [email protected] */ class Data2Test extends PHPUnit_Framework_TestCase { public function testA() { return 78; } /** * @dataProvider add_provider * @depends testB */ public function testB($a, $b, $c) { $this->assertEquals($c, $a + $b); return $a; } /** * @depneds testB */ public function testC($a) { var_dump($a); } public function add_provider() { return array ( array (0, 0, 0), array (0, 1, 1), array (1, 0, 1), array (1, 1, 2), ); } }
解釋一下:
testB 依賴於 testA (testA 使用了dataProvider 提供數據)
如果 add_provider 提供的數據至少有一次是成功的,則在成功一次後運行 testC
如果 add_provider 提供的數據沒有一次是成功的,則 testC 一次也不會執行
但是 testC 執行的結果永遠是 null, 因爲 $a 是通過 dataProvider 提供的。不能傳入依賴它的測試方法中
好像還是不太明白,反正我是盡力了。慢慢理解吧。通過構造迭代器來爲方法提供數據
通過標籤 @dataProvider 來直接返回數據作爲數據提供者,PHPUnit 也可以通過返回構造器對象來提供數據。上代碼
新建 IteratorTest.php 文件<?php /** * 通過構造迭代器來爲方法提供數據 * @author [email protected] */ class myIterator implements Iterator { private $_position = 0; private $_array = array ( array (0, 0, 0), array (0, 1, 1), array (1, 0, 1), array (1, 1, 2) ); public function current() { echo 0; return $this->_array[$this->_position]; } public function key() { echo 1; return $this->_position; } public function next() { echo 2; ++$this->_position; } public function valid() { echo 3; return isset($this->_array[$this->_position]); } public function rewind() { echo 4; return $this->_position = 0; } }
/** * 測試類 */ class IteratorTest extends PHPUnit_Framework_TestCase { /** * @dataProvider add_provider */ public function testAdd($a, $b, $c) { $this->assertEquals($c, $a + $b); } public function add_provider() { return new myIterator(); } }
先定義了一個構造器,然後返回這個構造器對象,同樣也能提供數據。運行 phpunit IteratorTest.php
運行結果如下:Time: 408 ms, Memory: 8.25Mb OK (4 tests, 4 assertions)
異常測試
有時候我們希望能夠拋出我們所期待的異常。這裏有三種方法來測試異常,上代碼
定義 ThrowTest 類保存到文件<?php /** * 測試異常 * 三種方法 * @author [email protected] */ class ThrowTest extends PHPUnit_Framework_TestCase { /** * 1.註釋法: expectedException 期待的異常 * @expectedExeption My_Exception */ public function testException1() { } /** * 2.設定法:$this->setExpectedException 期待的異常 */ public function testException2() { $this->setExpectedException('My_Exception'); } /** * 3.捕獲法:try catch */ public function testException3() { try { //代碼 } catch (My_Exception $e) { //捕獲到異常測試通過,否則失敗 return ; } $this->fail('一個期望的異常沒有被捕獲'); } }
代碼應該很明白了,不用解釋了。
錯誤測試
有時候代碼會發生錯誤,比如某個php文件找不到,文件不可讀,php 文件加載失敗等。這個時候我們也能進行測試,是否發生錯誤;PHPUnit 會把錯誤直接轉化爲異常PHPUnit_Framework_Error並拋出;我們要做到的是捕獲這個異常。上代碼。
定義 ErrorTest 類保存到文件中<?php /** * 錯誤測試 * phpunit 會把錯誤直接轉化爲異常PHPUnit_Framework_Error並拋出 */ class ErrorTest extends PHPUnit_Framework_TestCase { /** * 期待捕獲 PHPUnit_Framework_Error 的異常 * @expectedException PHPUnit_Framework_Error */ public function testError() { //如果文件不存在就會拋出異常,我們需要捕獲異常 include '../test.php'; } }
測試顯示OK,則證明已經捕獲。
對輸出進行測試
有時候我們需要對程序的指定輸出進行測試。比如echo 還是 print() 指定的值是否正確。
定義類 OutputTest 類保存到文件<?php /** * 輸出測試 * @author [email protected] */ class OutputTest extends PHPUnit_Framework_TestCase { public function testExpectFooActualFoo() { $this->expectOutputString('foo'); print 'foo'; } public function testExpectBarActualBaz() { $this->expectOutputString('bar'); print 'baz'; } }
注意
在嚴格模式下,本身產生輸出的測試將會失敗。基鏡(fixture)
PHPUnit 支持共享建立基境的代碼。在運行某個測試方法前,會調用一個名叫 setUp() 的模板方法。setUp() 是創建測試所用對象的地方。當測試方法運行結束後,不管是成功還是失敗,都會調用另外一個名叫 tearDown() 的模板方法。tearDown() 是清理測試所用對象的地方。<?php class FixTureTest extends PHPUnit_Framework_TestCase { protected $stack; protected function setUp() { $this->stack = array(); } public function testEmpty() { $this->assertTrue(empty($this->stack)); } public function testPush() { array_push($this->stack, 'foo'); $this->assertEquals('foo', $this->stack[count($this->stack)-1]); $this->assertFalse(empty($this->stack)); } public function testPop() { array_push($this->stack, 'foo'); $this->assertEquals('foo', array_pop($this->stack)); $this->assertTrue(empty($this->stack)); } }
以上是PHPUnit 官方的一個代碼示例。
測試類的每個測試方法都會運行一次 setUp() 和 tearDown() 模板方法(同時,每個測試方法都是在一個全新的測試類實例上運行的)。
另外,setUpBeforeClass() 與 tearDownAfterClass() 模板方法將分別在測試用例類的第一個測試運行之前和測試用例類的最後一個測試運行之後調用。setUp() 多 tearDown() 少
理論上說,setUp() 和 tearDown() 是精確對稱的,但是實踐中並非如此。實際上,只有在 setUp() 中分配了諸如文件或套接字之類的外部資源時才需要實現 tearDown() 。如果 setUp() 中只創建純 PHP 對象,通常可以略過 tearDown()。不過,如果在 setUp() 中創建了大量對象,你可能想要在 tearDown() 中 unset() 指向這些對象的變量,這樣它們就可以被垃圾回收機制回收掉。對測試用例對象的垃圾回收動作則是不可預知的。 —PHPUnit 官方網站
三、總結
好了,今天大概就先介紹這些,已經可以大概寫一些測試用例了。當然還有更高級的測試使用方法。現在爲什麼不講呢,因爲我也不會。。