Laravel 测试: PHPUnit 入门教程
介紹 PHPUnit 測試的基礎(chǔ)知識,使用基本的 PHPUnit 斷言和 Laravel 測試助手。
介紹
PHPUnit?是最古老和最著名的 PHP 單元測試包之一。它主要用于單元測試,這意味著可以用盡可能小的組件測試代碼,但是它也非常靈活,可以用于很多不僅僅是單元測試。
PHPUnit 包含許多簡單和靈活的斷言允許您輕松地測試代碼,當(dāng)您測試特定的組件時,這些斷言非常有效。但是,它確實(shí)意味著測試更高級的代碼(如控制器和表單提交驗(yàn)證)可能會復(fù)雜得多。
為了幫助開發(fā)人員更容易地進(jìn)行開發(fā), Laravel 框架 ?包含了一系列 應(yīng)用程序測試幫助程序 ,允許您編寫非常簡單的 PHPUnit 測試來測試應(yīng)用程序的復(fù)雜部分。
本教程的目的是向您介紹 PHPUnit 測試的基礎(chǔ)知識,使用默認(rèn) PHPUnit 斷言和 Laravel 測試助手。這樣做的目的是在本教程結(jié)束時,您可以自信地為應(yīng)用程序編寫基本測試。
前提
本教程假設(shè)您已經(jīng)熟悉 Laravel 并知道如何在應(yīng)用程序目錄中運(yùn)行命令(例如?php artisan?命令)。我們將創(chuàng)建幾個基本的示例類來學(xué)習(xí)不同的測試工具如何工作,因此建議您為本教程創(chuàng)建一個新的應(yīng)用程序。
如果已經(jīng)安裝了 Laravel ,則可以通過運(yùn)行以下命令創(chuàng)建新的測試應(yīng)用程序:
laravel new phpunit-tests復(fù)制代碼或者,您可以直接使用?Composer?創(chuàng)建新應(yīng)用程序:
composer create-project laravel/laravel --prefer-dist復(fù)制代碼其他安裝方法也可以在?Laravel 文檔中找到。
創(chuàng)建一個新的測試
使用 PHPUnit 的第一步是創(chuàng)建一個新的測試類。測試類的約定是它們存儲在應(yīng)用程序目錄的?./tests/?下。在這個文件夾中,每個測試類都被命名為?<name>Test.php 。這種格式允許 PHPUnit 查找每個測試類---它將忽略任何不以?Test.php 結(jié)尾的文件。
在新的 Laravel 應(yīng)用程序中,你會注意到?./tests/?目錄中有兩個文件: ?ExampleTest.php?和?TestCase.php. ?TestCase.php?文件是一個引導(dǎo)文件用于在我們的測試中設(shè)置 Laravel 環(huán)境。這允許我們在測試中使用 Laravel Facades 并為測試助手提供框架,我們將在稍后介紹。?ExampleTest.php?是一個示例測試類,其中包含使用應(yīng)用程序測試助手的基本測試用例-暫時忽略它。
要創(chuàng)建一個新的測試類,我們可以手動創(chuàng)建一個新文件,或者運(yùn)行由 Laravel 提供的 Artisan 命令?make:test
為了創(chuàng)建一個名為?BasicTest 的測試類,我們只需要運(yùn)行這個 artisan 命令:
php artisan make:test BasicTest復(fù)制代碼Laravel 將創(chuàng)建一個如下所示的基本測試類:
class BasicTest extends TestCase {/*** 一個基本的測試示例。** @return void*/public function testExample(){$this->assertTrue(true);} }復(fù)制代碼這里要注意的最重要的事情是?test?方法名稱上的前綴,與?Test 類名后綴一樣,這樣?test?前綴告訴 PHPUnit 在測試時運(yùn)行哪些方法。如果您忘記了?test?前綴,那么 PHPUnit 將忽略該方法。
在我們第一次運(yùn)行測試套件之前,有必要指出 Laravel 提供的默認(rèn)?phpunit.xml 文件。 PHPUnit 在運(yùn)行時會自動在當(dāng)前目錄中查找名為 phpunit.xml?或者?phpunit.xml.dist?的文件。您可以在此處配置測試的特定選項(xiàng)。
這個文件中有很多信息,但是現(xiàn)在最重要的部分是在?testsuite?目錄定義:
xml version="1.0" encoding="UTF-8" <phpunit ... ><testsuites><testsuite name="Application Test Suite"><directory>./tests/</directory></testsuite></testsuites>... </phpunit>復(fù)制代碼這將告訴 PHPUnit 運(yùn)行時在?./tests/?目錄中找到的測試,正如我們之前所知,這是存儲測試的約定。
現(xiàn)在我們已經(jīng)創(chuàng)建了一個基本測試,并且知道了 PHPUnit 配置,現(xiàn)在是第一次運(yùn)行測試的時候了。
您可以通過運(yùn)行以下?phpunit?命令來運(yùn)行測試:
./vendor/bin/phpunit復(fù)制代碼您應(yīng)該看到與此類似的輸出:
PHPUnit 4.8.19 by Sebastian Bergmann and contributors...Time: 103 ms, Memory: 12.75MbOK (2 tests, 3 assertions)復(fù)制代碼現(xiàn)在我們已經(jīng)有了一個有效的 PHPUnit 設(shè)置,現(xiàn)在是時候開始編寫一個基本測試了。
注意,它會統(tǒng)計(jì)2個測試和3個斷言,因?yàn)?ExampleTest.php?文件包含了一個帶有兩個斷言的測試。我們的新基本測試包括一個單獨(dú)的斷言,該斷言已通過。
寫一個基礎(chǔ)測試
為了幫助 PHPUnit 提供的基本斷言,我們將首先創(chuàng)建一個提供一些簡單功能的基本類
在?./app/?目錄中創(chuàng)建一個名為?Box.php 的新文件,并復(fù)制此示例類:
namespace App;class Box {/*** @var array*/protected $items = [];/*** 使用給定項(xiàng)構(gòu)造框** @param array $items*/public function __construct($items = []){$this->items = $items;}/*** 檢查指定的項(xiàng)目是否在框中。** @param string $item* @return bool*/public function has($item){return in_array($item, $this->items);}/*** 從框中移除項(xiàng),如果框?yàn)榭?#xff0c;則為 null 。** @return string*/public function takeOne(){return array_shift($this->items);}/*** 從包含指定字母開頭的框中檢索所有項(xiàng)目。** @param string $letter* @return array*/public function startsWith($letter){return array_filter($this->items, function ($item) use ($letter) {return stripos($item, $letter) === 0;});} }復(fù)制代碼接下來, 打開你的?./tests/BasicTest.php?類(我們之前創(chuàng)建的類),并刪除默認(rèn)創(chuàng)建的?testExample?方法, 你應(yīng)該留一個空類。
我們現(xiàn)在將使用七個基本的 PHPUnit 斷言來為我們的?Box?類編寫測試。這些斷言是:
- assertTrue()
- assertFalse()
- assertEquals()
- assertNull()
- assertContains()
- assertCount()
- assertEmpty()
assertTrue() 和 assertFalse()
assertTrue()?和?assertFalse()?允許你聲明一個值等于 true 或 false 。這意味著它們非常適合測試返回布爾值的方法。在我們的?Box 類中,我們有一個名為?has($item) 的方法,當(dāng)指定的項(xiàng)在 box 中或不在 box 中時,該方法返回對應(yīng)返回 true 或 false .
要在 PHPUnit 中為此編寫測試,我們可以執(zhí)行以下操作:
use App\Box;class BasicTest extends TestCase {public function testHasItemInBox(){$box = new Box(['cat', 'toy', 'torch']);$this->assertTrue($box->has('toy'));$this->assertFalse($box->has('ball'));} }復(fù)制代碼注意我們?nèi)绾沃粚⒁粋€參數(shù)傳遞給?assertTrue()?和?assertFalse()?方法,并且它是?has($item)?方法的輸入.
如果您現(xiàn)在運(yùn)行?./vendor/bin/phpunit?命令,您會注意到輸出包括:
OK (2 tests, 4 assertions)復(fù)制代碼這意味著我們的測試已經(jīng)通過。
如果您將?assertFalse()?替換成?assertTrue()?并運(yùn)行?phpunit?命令,輸出將如下所示:
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.F.Time: 93 ms, Memory: 13.00MbThere was 1 failure:1) BasicTest::testHasItemInBox Failed asserting that false is true../tests/BasicTest.php:12FAILURES! Tests: 2, Assertions: 4, Failures: 1.復(fù)制代碼這告訴我們第12行的斷言未能斷言?false?值是?true?- 因?yàn)槲覀儗?assertFalse()?替換為?assertTrue() 。
將其交換回來,然后重新運(yùn)行 PHPUnit 。測試應(yīng)該再次通過,因?yàn)槲覀円呀?jīng)修復(fù)了破損的測試。
assertEquals() 與 assertNull()
接下來,讓我們看看?assertEquals(), 以及?assertNull()。
assertEquals() 用于比較變量實(shí)際值與預(yù)期值是否相等。我們用它來檢查 takeOne() 方法的返回值是否為 Box 內(nèi)的當(dāng)前值。當(dāng) Box 為空時,takeOne() 將返回 null,我們亦可使用 assertNull() 來進(jìn)行檢查。
與?assertTrue()、assertFalse() 以及?assertNull() 不同,assertEquals()?需要兩個參數(shù)。第一個參數(shù)為 預(yù)期 值,第二個參數(shù)則為 實(shí)際 值。
可參照如下代碼實(shí)現(xiàn)以上斷言(assertions):
use App\Box;class BasicTest extends TestCase {public function testHasItemInBox(){$box = new Box(['cat', 'toy', 'torch']);$this->assertTrue($box->has('toy'));$this->assertFalse($box->has('ball'));}public function testTakeOneFromTheBox(){$box = new Box(['torch']);$this->assertEquals('torch', $box->takeOne());// 當(dāng)前 Box 為空,應(yīng)當(dāng)為 Null$this->assertNull($box->takeOne());} }復(fù)制代碼運(yùn)行?phpunit?命令,你應(yīng)當(dāng)看到如下輸出:
OK (3 tests, 6 assertions)復(fù)制代碼assertContains() 和 assertCount() 以及 assertEmpty()
終于,我們有三個作用于數(shù)組有關(guān)的斷言,我們能夠使用它們?nèi)z查?Box?類中的 ?startsWith($item) 方法。?assertContains()?斷言傳遞進(jìn)來的數(shù)組中包含指定值,?assertCount()?斷言數(shù)組的項(xiàng)數(shù)為指定數(shù)量,assertEmpty() 斷言傳遞進(jìn)來的數(shù)組為空。
讓我們來執(zhí)行以下測試:
use App\Box;class BasicTest extends TestCase {public function testHasItemInBox(){$box = new Box(['cat', 'toy', 'torch']);$this->assertTrue($box->has('toy'));$this->assertFalse($box->has('ball'));}public function testTakeOneFromTheBox(){$box = new Box(['torch']);$this->assertEquals('torch', $box->takeOne());// Null,現(xiàn)在這個 box 是空的。$this->assertNull($box->takeOne());}public function testStartsWithALetter(){$box = new Box(['toy', 'torch', 'ball', 'cat', 'tissue']);$results = $box->startsWith('t');$this->assertCount(3, $results);$this->assertContains('toy', $results);$this->assertContains('torch', $results);$this->assertContains('tissue', $results);// 如果傳遞復(fù)數(shù)斷言數(shù)組為空$this->assertEmpty($box->startsWith('s'));} }復(fù)制代碼保存并再一次運(yùn)行你的測試:
OK (4 tests, 9 assertions)復(fù)制代碼恭喜你,你剛剛使用七個基礎(chǔ)的 PHPUnit 斷言完成了對 Box 類的全部測試。通過這些簡單的斷言你能夠做許多事,對于其他斷言,大多數(shù)要更復(fù)雜,不過它們?nèi)宰裱陨鲜褂靡?guī)則。
測試你的程序
在你的程序里,對每個組件進(jìn)行單元測試在很多情況下都是有必要的,而且也應(yīng)該成為你開發(fā)過程中必不可少的一部分,但這并不是你需要做的全部的測試。當(dāng)你構(gòu)建一個包含復(fù)雜視圖、導(dǎo)航和表單的程序時,你同樣想測試這些組件。這時,Laravel的測試助手可以使這些測試像單元測試簡單組件一樣容易。
我們之前查看在?./tests/?目錄下的默認(rèn)文件時跳過了 ./tests/ExampleTest.php?文件。 現(xiàn)在打開它,內(nèi)容如下所示:
class ExampleTest extends TestCase {/** * 一個基本功能測試示例。 * * @return void */public function testBasicExample(){$this->visit('/')->see('Laravel 5');} }復(fù)制代碼我們可以看到這個測試示例非常簡單。在不知道測試助手如何運(yùn)作的情況下,我們可以猜測它的意思如下:
如果你打開你的web瀏覽器,訪問我們的程序(如果你沒有啟動你的web服務(wù)器,你可以運(yùn)行?php artisan serve?),你應(yīng)該可以在web根目錄上看到屏幕上有“Laravel 5”的文本。 鑒于這個測試已經(jīng)通過了PHPUnit,我們可以很確定地說我們對這個測試示例改造是正確的。
這個測試確保了訪問/路徑,網(wǎng)頁可以返回“'Laravel 5”的文本。一個如此簡單的檢查也許不代表什么,但如果你的網(wǎng)站上要顯示關(guān)鍵信息,它就可以在一個別處的改動導(dǎo)致這個頁面無法正常顯示正確的信息時,防止你部署一個被損壞的程序。
visit()、see() 以及 dontSee()
現(xiàn)在嘗試編寫自己的測試,更進(jìn)一步理解它吧。
首先,編輯?./app/Http/routes.php?,增加一個新的路由。為了教程目的,我們創(chuàng)建希臘字母定義的路由:
Route::get('/',function () {return view('welcome'); });Route::get('/alpha',function () {return view('alpha'); });復(fù)制代碼然后,創(chuàng)建視圖文件?./resources/views/alpha.blade.php,使用 Alpha 作為關(guān)鍵字,保存基本的HTML文件:
<html><head><title>Alpha</title></head><body><p>This is the Alpha page.</p></body> </html>復(fù)制代碼打開瀏覽器,輸入網(wǎng)址:?http://localhost:8000/beta,頁面會顯示出 "This is the Alpha page." 的內(nèi)容。
現(xiàn)在我們有了測試用到的模版文件,下一步,我們通過運(yùn)行命令?make:test?來創(chuàng)建一個新的測試文件:
php artisan make:test AlphaTest復(fù)制代碼然后變成剛創(chuàng)建好的測試文件,按照框架提供的例子,測試 "alpha" 頁面上沒有包含 "beta" 。 我們可以使用方法?dontSee()?,它是?see() 的對應(yīng)的反向方法。
下面代碼是上面實(shí)現(xiàn)的簡單例子:
class AlphaTest extends TestCase {public function testDisplaysAlpha(){$this->visit('/alpha')->see('Alpha')->dontSee('Beta');} }復(fù)制代碼保存并運(yùn)行 PHPUnit (./vendor/bin/phpunit),測試代碼應(yīng)該會全部通過,你會看到像這樣的測試狀態(tài)內(nèi)容顯示:
OK (5 tests,12 assertions)復(fù)制代碼開發(fā)前先寫測試
對于測試來說,測試驅(qū)動開發(fā)?(TDD) 是非常酷的方法,首先我們先寫測試。寫完測試并執(zhí)行它們,你會發(fā)現(xiàn)測試沒通過,接下來?我們編寫滿足測試的代碼,再次執(zhí)行測試,使測試通過。 接下來讓我們開始。
首先,建立一個?BetaTest?類使用?make:test?artisan 命令:
php artisan make:test BetaTest復(fù)制代碼接下來,更新測試用例以便檢查?/beta?的路由 route 為「Beta」:
class BetaTest extends TestCase {public function testDisplaysBeta(){$this->visit('/beta')->see('Beta')->dontSee('Alpha');} }復(fù)制代碼現(xiàn)在使用?./vendor/bin/phpunit 命令來執(zhí)行測試。結(jié)果是一個看起來簡潔但不好的錯誤信息,如下:
> ./vendor/bin/phpunit PHPUnit 4.8.19 by Sebastian Bergmann and contributors.....F.Time: 144 ms, Memory: 14.25MbThere was 1 failure:1) BetaTest::testDisplaysBeta 一個對 [http://localhost/beta] 的請求失敗了。收到狀態(tài)碼 [404]。...FAILURES! Tests: 6, Assertions: 13, Failures: 1.復(fù)制代碼我們現(xiàn)在需要創(chuàng)建這個不存在的路由。讓我們開始。
首先,編輯?./app/Http/routes.php?文件來創(chuàng)建新的 /beta?路由:
Route::get('/', function () {return view('welcome'); });Route::get('/alpha', function () {return view('alpha'); });Route::get('/beta', function () {return view('beta'); });復(fù)制代碼接下來,在 ./resources/views/beta.blade.php 下創(chuàng)建如下視圖模版:
<html><head><title>Beta</title></head><body><p>This is the Beta page.</p></body> </html>復(fù)制代碼現(xiàn)在再一次執(zhí)行 PHPUnit,結(jié)果應(yīng)該再一次回到綠色。
> ./vendor/bin/phpunit PHPUnit 4.8.19 by Sebastian Bergmann and contributors.......Time: 142 ms, Memory: 14.00MbOK (6 tests, 15 assertions)復(fù)制代碼這樣我們就通過在完成新的頁面之前寫測試的方式,對 測試驅(qū)動開發(fā) 進(jìn)行了實(shí)踐。
click() 和 seePageIs()
Laravel 也提供一個輔助函數(shù) (click()) 允許測試點(diǎn)擊頁面中存在的連接 ,以及一個方法 (seePageIs()) 檢查點(diǎn)擊展示的結(jié)果頁面。
讓我們使用這兩個輔助函數(shù)去執(zhí)行在 Alpha 和 Beta 頁面的鏈接。
首先,我們更新我們的測試。打開?AlphaTest?類,我們將添加一個新的測試方法,這將點(diǎn)擊 「alpha」頁面上的「Next」鏈接跳轉(zhuǎn)到 「beta」頁面。
新的測試代碼如下:
class AlphaTest extends TestCase {public function testDisplaysAlpha(){$this->visit('/alpha')->see('Alpha')->dontSee('Beta');}public function testClickNextForBeta(){$this->visit('/alpha')->click('Next')->seePageIs('/beta');} }復(fù)制代碼注意到,在我們新建的 testClickNextForBeta() 方法中,我們并沒有檢查每一個頁面的內(nèi)容。 其他測試都成功的檢查了兩個頁面的內(nèi)容,所以這里我們只關(guān)心點(diǎn)擊 「Next」鏈接將發(fā)送到?/beta。
你現(xiàn)在可以運(yùn)行測試組件了,但就像預(yù)料的一樣測試將不通過,因?yàn)槲覀冞€沒有更新我們的 HTML。
接下來,我們將更新?BetaTest?來做類似的事情:
class BetaTest extends TestCase {public function testDisplaysBeta(){$this->visit('/beta')->see('Beta')->dontSee('Alpha');}public function testClickNextForAlpha(){$this->visit('/beta')->click('Previous')->seePageIs('/alpha');} }復(fù)制代碼接下來,我們更新我們的 HTML 模版。
./resources/views/alpha.blade.php:
<html><head><title>Alpha</title></head><body><p>This is the Alpha page.</p><p><a href="/beta">Next</a></p></body> </html>復(fù)制代碼./resources/views/beta.blade.php:
<html><head><title>Beta</title></head><body><p>This is the Beta page.</p><p><a href="/alpha">Previous</a></p></body> </html>復(fù)制代碼保存文件,再一次執(zhí)行 PHPUnit:
> ./vendor/bin/phpunit PHPUnit 4.8.19 by Sebastian Bergmann and contributors.F....F..Time: 175 ms, Memory: 14.00MbThere were 2 failures:1) AlphaTest::testDisplaysAlpha Failed asserting that '<!DOCTYPE html> <html><head><title>Alpha</title></head><body><p>This is the Alpha page.</p><p><a href="/beta">Next</a></p></body> </html> ' does not match PCRE pattern "/Beta/i".2) BetaTest::testDisplaysBeta Failed asserting that '<!DOCTYPE html> <html><head><title>Beta</title></head><body><p>This is the Beta page.</p><p><a href="/alpha">Previous</a></p></body> </html> ' does not match PCRE pattern "/Alpha/i".FAILURES! Tests: 8, Assertions: 23, Failures: 2.復(fù)制代碼然而測試失敗了。如果你仔細(xì)觀察我們的新 HTML,你將注意到我們分別有術(shù)語?beta?和?alpha?在?/alpha?和?/beta?頁面。這意味著我們需要稍微更改我們的測試讓它們與誤報(bào)不匹配。
在每一個?AlphaTest?和?BetaTest?類,更新?testDisplays* 方法去使用?dontSee('<page> page')。通過這種方式,這將僅僅匹配字符串而不是那個術(shù)語。
兩個測試文件如下所示:
./tests/AlphaTest.php:
class AlphaTest extends TestCase {public function testDisplaysAlpha(){$this->visit('/alpha')->see('Alpha')->dontSee('Beta page');}public function testClickNextForBeta(){$this->visit('/alpha')->click('Next')->seePageIs('/beta');} }復(fù)制代碼./tests/BetaTest.php:
class BetaTest extends TestCase {public function testDisplaysBeta(){$this->visit('/beta')->see('Beta')->dontSee('Alpha page');}public function testClickNextForAlpha(){$this->visit('/beta')->click('Previous')->seePageIs('/alpha');} }復(fù)制代碼再一次運(yùn)行你的測試,所有的測試都應(yīng)該通過了。我們現(xiàn)在已經(jīng)測試我們所有的新文件,包括頁面中的 Next/Previous 鏈接。
通過 Semaphore 對 PHPUnit 持續(xù)集成
通過 Semaphore設(shè)置?持續(xù)集成你可以自動執(zhí)行你的測試。
這樣每一次你進(jìn)行?git push?提交代碼的時候都會執(zhí)行你的測試,并且 Semaphore 預(yù)裝了所有最新的 PHP 版本。
如果你還沒有一個 Semaphore 賬戶, 先去 注冊一個免費(fèi)的 Semaphore 賬戶?。接下來需要做的是將它?添加到你的項(xiàng)目,并按照提示逐步去做來執(zhí)行你的測試:
composer install --prefer-source phpunit復(fù)制代碼關(guān)于?PHP 持續(xù)集成 的更多信息,請參照 Semaphore 文檔。
結(jié)語
你應(yīng)該注意到本教程中的所有測試都有一個共同的主題:它們都非常簡單。 這是學(xué)習(xí)如何使用基本的測試斷言和輔助函數(shù),并且盡可能的使用它們的好處之一。編寫測試越簡單,測試就越容易理解和維護(hù)。
掌握了本教程中介紹的 PHPUnit 斷言之后,你還可以去 PHPUnit 文檔 找到更多內(nèi)容。 所有的斷言都遵循基本的模式,但你會發(fā)現(xiàn),在大多數(shù)測試中都會返回基本的斷言。
對于 PHPUnit 斷言來說,Laravel 的測試輔助函數(shù)是極好的補(bǔ)充,這讓應(yīng)用程序的測試變的非常容易。也就是說,重要的是要認(rèn)識到,對于我們寫測試,我們只檢查關(guān)鍵信息,而不是整個頁面。這使得測試變得簡單,并允許頁面內(nèi)容隨著應(yīng)用程序的變化而變化。如果關(guān)鍵信息仍然存在,測試仍然通過,每個人都會滿意。
文章轉(zhuǎn)自: https://learnku.com/laravel/t/24559
更多文章:https://learnku.com/laravel/c/translations
總結(jié)
以上是生活随笔為你收集整理的Laravel 测试: PHPUnit 入门教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: react学习(18)----vs 全局
- 下一篇: 黑马程序员顺义校区php_传智播客·黑马