一、概述
1. 什么是單元測試?
- 【百度百科】單元測試是對軟件中的最小可測單元進行檢查和驗證混坞。
- 是開發(fā)者編寫的一小段代碼,用于檢驗被測代碼的一個很小的钢坦、很明確的功能是否正確究孕。
2. 作用是什么?
- 【廢話】檢查軟件爹凹、程序的可行性厨诸,穩(wěn)定性。
- 通過單元測試能夠避免在迭代禾酱、升級等過程中微酬,引起重復的、多余的問題颤陶。
- 避免在別人修改代碼的時候颗管,影響到你的邏輯
3. 哪些程序需要寫單元測試(PHP)?
- 【理想】理想的單元測試應當覆蓋程序中所有可能的路徑指郁,包括正確的和錯誤的路徑忙上,個單元測試通常覆蓋一個函數(shù)或方法中的一個特定路徑拷呆。
- 【現(xiàn)實】model闲坎、helper、controller中的函數(shù)必須測試茬斧、路徑覆蓋到所有可能性
二腰懂、PHPUnit的安裝與集成CI框架
- 略。项秉。绣溜。。娄蔼。后續(xù)再補
三怖喻、PHPUnit的使用
編寫測試用例
-
測試的依賴關系 @depends
PHPUnit支持對測試方法之間的顯式依賴關系進行聲明底哗。這種依賴關系并不是定義在測試方法的執(zhí)行順序中,而是允許生產(chǎn)者(producer)返回一個測試基境(fixture)的實例锚沸,并將此實例傳遞給依賴于它的消費者(consumer)們跋选。
<?php class StackTest extends PHPUnit_Framework_TestCase { public function testEmpty() { $stack = array(); $this->assertEmpty($stack); return $stack; } /**
-
@depends testEmpty
*/ public function testPush(array $stack) { array_push($stack, 'foo'); $this->assertEquals('foo', $stack[count($stack)-1]); $this->assertNotEmpty($stack); return $stack; } /**
-
@depends testPush
*/ public function testPop(array $stack) { $this->assertEquals('foo', array_pop($stack)); $this->assertEmpty($stack); }
}
?>
默認情況下,生產(chǎn)者所產(chǎn)生的返回值將“原樣”傳遞給相應的消費者哗蜈。這意味著前标,如果生產(chǎn)者返回的是一個對象,那么傳遞給消費者的將是一個指向此對象的引用距潘。如果需要傳遞對象的副本而非引用炼列,則應當用 @depends clone 替代 @depends。
-
-
-
數(shù)據(jù)供給器 @dataProvider
測試方法可以接受任意參數(shù)音比。用 @dataProvider 標注來指定使用哪個數(shù)據(jù)供給器方法俭尖。
數(shù)據(jù)供給器方法必須聲明為 public,其返回值要么是一個數(shù)組硅确,其每個元素也是數(shù)組目溉;要么是一個實現(xiàn)了 Iterator 接口的對象,在對它進行迭代時每步產(chǎn)生一個數(shù)組菱农。每個數(shù)組都是測試數(shù)據(jù)集的一部分,將以它的內(nèi)容作為參數(shù)來調(diào)用測試方法循未。<?php class DataTest extends PHPUnit_Framework_TestCase { /**
-
@dataProvider additionProvider
*/ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return array( array(0, 0, 0), array(0, 1, 1), array(1, 0, 1), array(1, 1, 3) ); }
}
?>
也可以是這樣:
<?php class DataTest extends PHPUnit_Framework_TestCase { /** * @dataProvider additionProvider */ public function testAdd($a, $b, $expected) { $this->assertEquals($expected, $a + $b); } public function additionProvider() { return array( 'adding zeros' => array(0, 0, 0), 'zero plus one' => array(0, 1, 1), 'one plus zero' => array(1, 0, 1), 'one plus one' => array(1, 1, 3) ); } } ?>
-
-
對PHP錯誤進行測試
默認情況下陷猫,PHPUnit 將測試在執(zhí)行中觸發(fā)的 PHP 錯誤零抬、警告、通知都轉(zhuǎn)換為異常宽涌。利用這些異常平夜,就可以,比如說卸亮,預期測試將觸發(fā) PHP 錯誤
<?php class ExpectedErrorTest extends PHPUnit_Framework_TestCase { /**
-
@expectedException PHPUnit_Framework_Error
*/ public function testFailingInclude() { include 'not_existing_file.php'; }
}
?>
測試phpunit -d error_reporting=2 ExpectedErrorTest
PHPUnit 5.2.0 by Sebastian Bergmann and contributors..
Time: 0 seconds, Memory: 5.25Mb
?
OK (1 test, 1 assertion)
-
命令行測試執(zhí)行器
說明
PHPUnit 測試執(zhí)行器可通過phpunit 調(diào)用忽妒,例如在CI中:
tongkundeMacBook-Pro:www tongkun$ cd tests/
tongkundeMacBook-Pro:tests tongkun$ phpunit
PHPUnit 5.0.0 by Sebastian Bergmann and contributors.
............. 13 / 13 (100%)
Time: 195 ms, Memory: 17.50Mb
OK (13 tests, 8 assertions)
說明:
先進入測試的根目錄,執(zhí)行phpunit 命令,后面可跟具體的目錄或文件段直,也可不跟吃溅,如果沒有則會對當前目錄的所有文件執(zhí)行單元測試,對于每個測試的運行鸯檬,PHPUnit命令行工具會輸出一個字符來指示進展:
- . 當測試陳宮時輸出
- F 當測試方法運行過程中一個斷言失敗時輸出罕偎,例如一個失敗的assertEquals()調(diào)用
- E 當測試方法運行過程中產(chǎn)生一個錯誤時輸出,錯誤是指意料之外的異常(exception)或者PHP錯誤
- R 當測試被標記有風險時輸出
- S 當測試跳出時輸出
- I 當測試被標記不完整或為實現(xiàn)時輸出
常用命令行選項
--coverage-clover:為運行的測試生成帶有代碼覆蓋率信息的 XML 格式的日志文件
--coverage-html:生成 HTML 格式的代碼覆蓋率報告
--coverage-php:生成一個序列化后的 PHP_CodeCoverage 對象京闰,此對象含有代碼覆蓋率信息
--log-json:生成 JSON 格式的日志文件
-
--filter:只運行名稱與給定模式匹配的測試颜及。如果模式未閉合包裹于分隔符,PHPUnit 將用 / 分隔符對其進行閉合包裹
tongkundeMacBook-Pro:tests tongkun$ phpunit --filter 'WelcomeTest::testTest' PHPUnit 5.0.0 by Sebastian Bergmann and contributors. ... 3 / 3 (100%) Time: 148 ms, Memory: 16.75Mb OK (3 tests, 3 assertions)
這樣測試的就是WelcomeTest類中的testTest函數(shù)蹂楣,過濾模式的例子有很多俏站,詳見文檔官方
-
--colors:使用彩色輸出。Windows下痊土,用 ANSICON 或 ConEmu肄扎。
本選項有三個可能的值:
never: 完全不使用彩色輸出。當未使用 --colors 選項時赁酝,這是默認值犯祠。
auto: 如果當前終端不支持彩色、或者輸出被管道輸出至其他命令酌呆、或輸出被重定向至文件時衡载,不使用彩色輸出,其余情況使用彩色隙袁。
always: 總是使用彩色輸出痰娱,即使當前終端不支持彩色、輸出被管道輸出至其他命令菩收、或輸出被重定向至文件梨睁。
當使用了 --colors 選項但未指定任何值時,將選擇 auto 做為其值娜饵。
--stop-on-error:首次錯誤出現(xiàn)后停止執(zhí)行坡贺。
--stop-on-failure:首次錯誤或失敗出現(xiàn)后停止執(zhí)行。
--stop-on-risky:首次碰到有風險的測試時停止執(zhí)行箱舞。
--stop-on-risky:首次碰到有風險的測試時停止執(zhí)行遍坟。
--stop-on-incomplete首次碰到不完整的測試時停止執(zhí)行。
-
--repeat:將測試重復運行指定次數(shù)褐缠。
tongkundeMacBook-Pro:tests tongkun$ phpunit --repeat 10 PHPUnit 5.0.0 by Sebastian Bergmann and contributors. ............................................................... 63 / 130 ( 48%) ............................................................... 126 / 130 ( 96%) .... 130 / 130 (100%) Time: 456 ms, Memory: 26.25Mb OK (130 tests, 80 assertions)
-
--tap:使用 Test Anything Protocol (TAP) 報告測試進度
tongkundeMacBook-Pro:tests tongkun$ phpunit --tap TAP version 13 ok 1 - CI_Unit_Test_class_Test::test_CI_Unit_Test_Class ok 2 - SomeControllerTest::testWelcomeController ok 3 - WelcomeTest::testIndex ok 4 - WelcomeTest::testTest ok 5 - WelcomeTest::testOutput ok 6 - WelcomeTest::testTest1 ok 7 - WelcomeTest::testTest2 ok 8 - HelperTest::testSampleFunction ok 9 - SomeLibTest::testMethod ok 10 - M_user_masterTest::testSelect ok 11 - M_user_masterTest::testInsert ok 12 - PHPTest::testFunctionJsonEncode ok 13 - PHPTest::testPhpVersion 1..13
-
--configuration, -c:從 XML 文件中讀取配置信息政鼠。更多細節(jié)請參見附錄 C风瘦。
如果 phpunit.xml 或 phpunit.xml.dist (按此順序)存在于當前工作目錄并且未使用 --configuration队魏,將自動從此文件中讀取配置。
tongkundeMacBook-Pro:tests tongkun$ phpunit --configuration phpunit.xml PHPUnit 5.0.0 by Sebastian Bergmann and contributors. ............. 13 / 13 (100%) Time: 209 ms, Memory: 17.50Mb OK (13 tests, 8 assertions)
--no-configuration:忽略當前工作目錄下的 phpunit.xml 與 phpunit.xml.dist。
四胡桨、基境(fixture)
什么是基境官帘?
“基境”就是編寫代碼來將整個場景設置成某個已知的狀態(tài),并在測試結(jié)束后將其復原到初始狀態(tài)昧谊。這個已知的狀態(tài)稱為測試的 基境(fixture)刽虹。
基境的建立
PHPUnit 支持共享建立基境的代碼。在運行某個測試方法前呢诬,會調(diào)用一個名叫 setUp() 的模板方法涌哲。setUp() 是創(chuàng)建測試所用對象的地方。當測試方法運行結(jié)束后尚镰,不管是成功還是失敗阀圾,都會調(diào)用另外一個名叫 tearDown() 的模板方法。tearDown() 是清理測試所用對象的地方狗唉。
<?php
class StackTest 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));
}
}
?>
測試類的每一個方法都會運行一次setUp()和tearDown()模板方法(同時初烘,每個測試方法都在一個全新的測試類實例上運行),
另外分俯,setUpBeforeClass() 與 tearDownAfterClass() 模板方法將分別在測試用例類的第一個測試運行之前和測試用例類的最后一個測試運行之后調(diào)用肾筐。基境共享可以在共享數(shù)據(jù)庫連接時使用缸剪;
五吗铐、組織測試
用文件系統(tǒng)來編排測試套件
例如:
?
phpunit controllers/WelcomeTest.php
用 XML 配置來編排測試套件
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
//配置文件
colors="true" //顏色
stopOnFailure="false" //出錯后是否終止
bootstrap="../application/third_party/CIUnit/bootstrap_phpunit.php"> //bootstrap 地址
<php>
<server name="SERVER_NAME" value="http://www.nyhdev.com" />
</php>
<testsuites>
//測試套件
<testsuite name="ControllerTests">
<directory>controllers</directory> //要測試的目錄
</testsuite>
<testsuite name="HelperTests">
<directory suffix=".php">helpers</directory>
</testsuite>
<testsuite name="LibTests">
<directory suffix=".php">libs</directory>
</testsuite>
<testsuite name="ModelTests">
<directory suffix=".php">models</directory>
</testsuite>
<testsuite name="SystemTests">
<directory suffix=".php">system</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory>vendor</directory>
<directory>libs</directory>
</blacklist>
<whitelist>
<directory>controllers</directory>
<directory>fixtures</directory>
<directory>models</directory>
<directory>helpers</directory>
</whitelist>
</filter>
</phpunit>
六、有風險的測試
無用測試
PHPUnit 可以更嚴格對待事實上不測試任何內(nèi)容的測試杏节。此項檢查可以用命令行選項 --report-useless-tests 或在 PHPUnit 的 XML 配置文件中設置 beStrictAboutTestsThatDoNotTestAnything="true" 來啟用抓歼。
在啟用本項檢查后,如果某個測試未進行任何斷言拢锹,它將被標記為有風險谣妻。仿件對象中的預期和諸如 @expectedException 這樣的標注同樣視為斷言。
測試執(zhí)行期間產(chǎn)生的輸出
PHPUnit 可以更嚴格對待測試執(zhí)行期間產(chǎn)生的輸出卒稳。 此項檢查可以用命令行選項 --disallow-test-output 或在 PHPUnit 的 XML 配置文件中設置 beStrictAboutOutputDuringTests="true" 來啟用蹋半。
在啟用本項檢查后,如果某個測試產(chǎn)生了輸出充坑,例如减江,在測試代碼或被測代碼中調(diào)用了 print,它將被標記為有風險捻爷。
七辈灼、未完成的測試與跳過的測試
未完成的測試
開始寫新的測試用例類時,可能想從寫下空測試方法開始也榄,比如:
public function testSomething()
{
}
假如把成功的測試視為綠燈巡莹、測試失敗視為紅燈司志,那么還額外需要黃燈來將測試標記為未完成或尚未實現(xiàn)。PHPUnit_Framework_IncompleteTest 是一個標記接口降宅,用于將測試方法拋出的異常標記為測試未完成或目前尚未實現(xiàn)而導致的結(jié)果骂远。PHPUnit_Framework_IncompleteTestError 是這個接口的標準實現(xiàn)。
例如:我們有一個測試文件腰根,contrllers/WelcomeTest.php激才,其中有一個測試方法,通過在測試方法中調(diào)用markTestIncomplete()將這個測試標記為未完成额嘿。
?
public function testTest() {
$this->assertTrue(true,'這里可以正常工作');
$this->markTestIncomplete('此測試尚未實現(xiàn)');
}
在PHPUnit命令行測試執(zhí)行器中輸出瘸恼,未完成的測試標記為1, 如下:
?
localhost:tests tongkun$ phpunit
PHPUnit 5.0.0 by Sebastian Bergmann and contributors.
R..I...RRRR.. 13 / 13 (100%)
Time: 187 ms, Memory: 17.75Mb
OK, but incomplete, skipped, or risky tests!
Tests: 13, Assertions: 8, Incomplete: 1, Risky: 5.
跳過測試
如上册养,如果有些測試需要某些環(huán)境或者配置才能完成钞脂,則可選擇跳過,通過調(diào)用 markTestSkipped() 方法來測試
用 @requires 來跳過測試
除了上述方法捕儒,還可以用 @requires 標注來表達測試用例的一些常見前提條件冰啃。
事例:
例 7.3: 用 @requires 來跳過測試
<?php
/**
* @requires extension mysqli
*/
class DatabaseTest extends PHPUnit_Framework_TestCase
{
/**
* @requires PHP 5.3
*/
public function testConnection()
{
// 測試要求有 mysqli 擴展,并且 PHP >= 5.3
}
// ... 所有其他要求有 mysqli 擴展的測試
}
?>
要求安裝mysqli苦戰(zhàn)和php 5.3 才能執(zhí)行
#常用斷言
前邊廢話一篇刘莹,終于到了關鍵的斷言部分阎毅,斷言可以說是單元測試的核心,通過斷言的校驗点弯,保證程序的正確運行扇调,并輸出正確的值。
-
assertArrayHasKey()
assertArrayHasKey(mixed $key, array $array[, string $message = ''])
當 $array 不包含 $key 時報告錯誤抢肛,錯誤訊息由 $message 指定狼钮。
assertArrayNotHasKey() 是與之相反的斷言,接受相同的參數(shù)捡絮。
-
assertContains()
assertContains(mixed $needle, Iterator|array $haystack[, string $message = ''])
當 $needle 不是 $haystack的元素時報告錯誤熬芜,錯誤訊息由 $message 指定。
assertNotContains() 是與之相反的斷言福稳,接受相同的參數(shù)涎拉。
assertContains(string $needle, string $haystack[, string $message = '', boolean $ignoreCase = FALSE])
當 $needle 不是 $haystack 的子字符串時報告錯誤,錯誤訊息由 $message 指定的圆。
-
assertContainsOnly()
assertContainsOnly(string $type, Iterator|array $haystack[, boolean $isNativeType = NULL, string $message = ''])
當 $haystack 并非僅包含類型為 $type 的變量時報告錯誤鼓拧,錯誤訊息由 $message 指定。
$isNativeType 是一個標志越妈,用來表明 $type 是否是原生 PHP 類型季俩。
-
assertEmpty()
assertEmpty(mixed $actual[, string $message = ''])
當 $actual 非空時報告錯誤,錯誤訊息由 $message 指定梅掠。
assertNotEmpty() 是與之相反的斷言酌住,接受相同的參數(shù)店归。
assertAttributeEmpty() 和 assertAttributeNotEmpty() 是便捷包裝(convenience wrapper),可以應用于某個類或?qū)ο蟮哪硞€ public赂韵、protected 或 private 屬性。
-
assertEquals()
assertEquals(mixed $expected, mixed $actual[, string $message = ''])
當兩個變量 $expected 和 $actual 不相等時報告錯誤挠蛉,錯誤訊息由 $message 指定祭示。
assertNotEquals() 是與之相反的斷言,接受相同的參數(shù)谴古。
注意特定類型的比較(浮點型等),詳見文檔
-
assertFalse()
assertFalse(bool $condition[, string $message = ''])
當 $condition 為 TRUE 時報告錯誤质涛,錯誤訊息由 $message 指定。
assertNotFalse() 是與之相反的斷言掰担,接受相同的參數(shù)汇陆。
-
assertNull()
assertNull(mixed $variable[, string $message = ''])
當 $actual 不是 NULL 時報告錯誤,錯誤訊息由 $message 指定带饱。
assertNotNull() 是與之相反的斷言毡代,接受相同的參數(shù)。
-
assertRegExp()
assertRegExp(string $pattern, string $string[, string $message = ''])當 $string 不匹配于正則表達式 $pattern 時報告錯誤勺疼,錯誤訊息由 $message 指定教寂。
assertNotRegExp() 是與之相反的斷言,接受相同的參數(shù)执庐。
-
assertStringMatchesFormat()
assertStringMatchesFormat(string $format, string $string[, string $message = ''])
當 $string 不匹配于 $format 定義的格式時報告錯誤酪耕,錯誤訊息由 $message 指定。
assertStringNotMatchesFormat() 是與之相反的斷言轨淌,接受相同的參數(shù)迂烁。
-
assertSame()
assertSame(mixed $expected, mixed $actual[, string $message = ''])當兩個變量 $expected 和 $actual 的值與類型不完全相同時報告錯誤,錯誤訊息由 $message 指定递鹉。
assertNotSame() 是與之相反的斷言盟步,接受相同的參數(shù)。
assertAttributeSame() 和 assertAttributeNotSame() 是便捷包裝(convenience wrapper)躏结,以某個類或?qū)ο蟮哪硞€ public址芯、protected 或 private 屬性作為實際值來進行比較。
-
assertTrue()
assertTrue(bool $condition[, string $message = ''])
當 $condition 為 FALSE 時報告錯誤窜觉,錯誤訊息由 $message 指定谷炸。
assertNotTrue() 是與之相反的斷言,接受相同的參數(shù)禀挫。
?