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