介紹 PHPUnit 測試的基礎知識,使用基本的 PHPUnit 斷言和 Laravel 測試助手。
PHPUnit 是最古老和最著名的 PHP 單元測試包之一。它主要用於單元測試,這意味着能夠用盡量小的組件測試代碼,可是它也很是靈活,能夠用於不少不只僅是單元測試。php
PHPUnit 包含許多簡單和靈活的斷言容許您輕鬆地測試代碼,當您測試特定的組件時,這些斷言很是有效。可是,它確實意味着測試更高級的代碼(如控制器和表單提交驗證)可能會複雜得多。html
爲了幫助開發人員更容易地進行開發, Laravel 框架 包含了一系列 應用程序測試幫助程序 ,容許您編寫很是簡單的 PHPUnit 測試來測試應用程序的複雜部分。laravel
本教程的目的是向您介紹 PHPUnit 測試的基礎知識,使用默認 PHPUnit 斷言和 Laravel 測試助手。這樣作的目的是在本教程結束時,您能夠自信地爲應用程序編寫基本測試。git
本教程假設您已經熟悉 Laravel 並知道如何在應用程序目錄中運行命令(例如 php artisan
命令)。咱們將建立幾個基本的示例類來學習不一樣的測試工具如何工做,所以建議您爲本教程建立一個新的應用程序。github
若是已經安裝了 Laravel ,則能夠經過運行如下命令建立新的測試應用程序:web
laravel new phpunit-tests
或者,您能夠直接使用 Composer 建立新應用程序:數組
composer create-project laravel/laravel --prefer-dist
其餘安裝方法也能夠在 Laravel 文檔中找到。瀏覽器
使用 PHPUnit 的第一步是建立一個新的測試類。測試類的約定是它們存儲在應用程序目錄的 ./tests/
下。在這個文件夾中,每一個測試類都被命名爲 <name>Test.php
。這種格式容許 PHPUnit 查找每一個測試類---它將忽略任何不以 Test.php
結尾的文件。bash
在新的 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 name="Application Test Suite"> <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()
容許你聲明一個值等於 true 或 false 。這意味着它們很是適合測試返回布爾值的方法。在咱們的 Box
類中,咱們有一個名爲 has($item)
的方法,當指定的項在 box 中或不在 box 中時,該方法返回對應返回 true 或 false .
要在 PHPUnit 中爲此編寫測試,咱們能夠執行如下操做:
<?php 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')); } }
注意咱們如何只將一個參數傳遞給 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()
用於比較變量實際值與預期值是否相等。咱們用它來檢查 takeOne()
方法的返回值是否爲 Box 內的當前值。當 Box 爲空時,takeOne()
將返回 null
,咱們亦可以使用 assertNull()
來進行檢查。
與 assertTrue()
、assertFalse()
以及 assertNull()
不一樣,assertEquals()
須要兩個參數。第一個參數爲 預期 值,第二個參數則爲 實際 值。
可參照以下代碼實現以上斷言(assertions
):
<?php 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()); // 當前 Box 爲空,應當爲 Null $this->assertNull($box->takeOne()); } }
運行 phpunit
命令,你應當看到以下輸出:
OK (3 tests, 6 assertions)
終於,咱們有三個做用於數組有關的斷言,咱們可以使用它們去檢查 Box
類中的 startsWith($item)
方法。 assertContains()
斷言傳遞進來的數組中包含指定值, assertCount()
斷言數組的項數爲指定數量,assertEmpty()
斷言傳遞進來的數組爲空。
讓咱們來執行如下測試:
<?php 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,如今這個 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'); } }
咱們能夠看到這個測試示例很是簡單。在不知道測試助手如何運做的狀況下,咱們能夠猜想它的意思以下:
/
(根目錄)若是你打開你的web瀏覽器,訪問咱們的程序(若是你沒有啓動你的web服務器,你能夠運行 php artisan serve
),你應該能夠在web根目錄上看到屏幕上有「Laravel 5」的文本。 鑑於這個測試已經經過了PHPUnit,咱們能夠很肯定地說咱們對這個測試示例改造是正確的。
這個測試確保了訪問/路徑,網頁能夠返回「'Laravel 5」的文本。一個如此簡單的檢查也許不表明什麼,但若是你的網站上要顯示關鍵信息,它就能夠在一個別處的改動致使這個頁面沒法正常顯示正確的信息時,防止你部署一個被損壞的程序。
如今嘗試編寫本身的測試,更進一步理解它吧。
首先,編輯 ./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文件:
<!DOCTYPE html> <html> <head> <title>Alpha</title> </head> <body> <p>This is the Alpha page.</p> </body> </html>
打開瀏覽器,輸入網址: 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
下建立以下視圖模版:
<!DOCTYPE html> <html> <head> <title>Beta</title> </head> <body> <p>This is the Beta page.</p> </body> </html>
如今再一次執行 PHPUnit,結果應該再一次回到綠色。
> ./vendor/bin/phpunit PHPUnit 4.8.19 by Sebastian Bergmann and contributors. ...... Time: 142 ms, Memory: 14.00Mb OK (6 tests, 15 assertions)
這樣咱們就經過在完成新的頁面以前寫測試的方式,對 測試驅動開發 進行了實踐。
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
:
<!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>
./resources/views/beta.blade.php
:
<!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>
保存文件,再一次執行 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 '<!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.
然而測試失敗了。若是你仔細觀察咱們的新 HTML,你將注意到咱們分別有術語 beta
和 alpha
在 /alpha
和 /beta
頁面。這意味着咱們須要稍微更改咱們的測試讓它們與誤報不匹配。
在每個 AlphaTest
和 BetaTest
類,更新 testDisplays*
方法去使用 dontSee('<page> page')
。經過這種方式,這將僅僅匹配字符串而不是那個術語。
兩個測試文件以下所示:
./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設置 持續集成你能夠自動執行你的測試。
這樣每一次你進行 git push
提交代碼的時候都會執行你的測試,而且 Semaphore 預裝了全部最新的 PHP 版本。
若是你尚未一個 Semaphore 帳戶, 先去 註冊一個免費的 Semaphore 帳戶 。接下來須要作的是將它 添加到你的項目,並按照提示逐步去作來執行你的測試:
composer install --prefer-source phpunit
關於 PHP 持續集成 的更多信息,請參照 Semaphore 文檔。
你應該注意到本教程中的全部測試都有一個共同的主題:它們都很是簡單。 這是學習如何使用基本的測試斷言和輔助函數,而且儘量的使用它們的好處之一。編寫測試越簡單,測試就越容易理解和維護。
掌握了本教程中介紹的 PHPUnit 斷言以後,你還能夠去 PHPUnit 文檔 找到更多內容。 全部的斷言都遵循基本的模式,但你會發現,在大多數測試中都會返回基本的斷言。
對於 PHPUnit 斷言來講,Laravel 的測試輔助函數是極好的補充,這讓應用程序的測試變的很是容易。也就是說,重要的是要認識到,對於咱們寫測試,咱們只檢查關鍵信息,而不是整個頁面。這使得測試變得簡單,並容許頁面內容隨着應用程序的變化而變化。若是關鍵信息仍然存在,測試仍然經過,每一個人都會滿意。
文章轉自: https://learnku.com/laravel/t...
更多文章: https://learnku.com/laravel/c...