Laravel 5.1單元測試(PHPUnit)入門
v1.0
作者:ZBW、ZGJ
簡介
PHP應用大多應用的單元測試框架是PHPUnit,這一框架也被Laravel集成了進來,並且Laravel增添了一些額外的功能以方便開發者進行Web相關的測試。本文將以項目中應用的單元測試為基礎,介紹Laravel下PHPUnit的相關內容。
注意到本文以Laravel 5.1為基礎,可能部分API在后續版本中有變動,但整體的使用方法變動不大。
安裝與配置
1. 安裝
通過composer配置composer.json中的依賴,並使用composer install
來安裝phpunit,安裝后的二進制文件在/vendor/bin/下。
(也可嘗試使用apt install phpunit
來安裝phpunit)
2. 配置
Laravel默認自帶了名為phpunit.xml
的配置文件,該文件已經為我們配置好了phpunit本身。
項目使用的配置文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="bootstrap/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">app/</directory>
</whitelist>
</filter>
<php>
<env name="APP_ENV" value="testing"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
</php>
<logging>
<log type="coverage-html" target="./tests/codeCoverage" charset="UTF-8"/>
</logging>
</phpunit>
其中需要注意以下內容:
-
<testsuites>/<directory>:這里存放了測試php代碼
-
<logging>:這里設置輸出測試結果的位置,輸出的內容是html版的覆蓋率報告,該報告非常詳細。
-
<filter>:該部分定義了phpunit可訪問的路徑,以上配置中我們只測試app文件夾下的內容。
基於以上內容,一個方便瀏覽測試報告的方式是,將tests/codeCoverage文件夾下的報告入口html文檔軟鏈接至public文件夾下,從而可以由瀏覽器直接訪問:
ln -s tests/codeCoverage public/tests
從而瀏覽器訪問路由:/tests即可看到測試報告。
編寫測試樣例
1. 新建測試樣例
規范的方法是使用artisan新建測試樣例:
php artisan make:test xxxTest
該命令會在tests文件夾下新建一個xxxTest.php,其中包含了一個默認測試函數。
2. 編寫函數的測試
在這部分內容中我們主要需要應用斷言來檢查函數的輸入和輸出是否匹配。斷言是phpunit本身就帶有的功能。
常用的一些斷言包括:
$this->assertTrue(表達式) //檢查表達式是否為真
$this->assertFalse(表達式) //檢查表達式是否為假
$this->assertEquals(X,Y) //檢查兩個變量是否相等
$this->assertFileExists(文件) //檢查文件是否存在
具體的斷言函數可參考PHPUnit文檔
3. 編寫Web功能測試
Laravel集成PHPUnit最方便的地方是其可以編寫關於Web控制器及功能的測試。
例如測試某個頁面訪問是否正常的測試內容如下:
public function testIndex()
{
$this->visit('logout') ;
$this->visit('/desexp')
->see("設計性實驗")
->see("請選擇實驗")
->see("D01");
$this->visit('/login')
->see('登錄')
->type($this->gen_admin_email , 'email')
->type($this->gen_admin_password , 'password')
->press('login-submit');
$this->visit('/desexp')
->see("設計性實驗")
->see("請選擇實驗")
->see("D01");
}
這部分測試編寫的方式是將所有需要運行的函數按先后順序串聯起來。
3.1 測試頁面訪問
一些訪問方面的函數如下,這部分函數一般用於測試訪問及與頁面進行交互。
visit('路由') // 訪問某個地址
see('xxx') //檢查訪問的頁面中是否出現了xxx
dontSee('xxx') //與see功能相反,檢查是否沒有出現xxx
type('輸入內容',輸入框name屬性) //輸入內容至輸入框
check('單選框name屬性') //選中checkbox
select(’內容‘,’下拉菜單區域‘) //選擇下拉菜單項
press('xxx') //按下指定元素或按鈕
attach('文件路徑','文件上傳name屬性') //附加文件
在上文的例子中,我們測試前可以選擇手動模擬登陸操作。
3.2 測試JSON API
測試JSON API時以上的函數往往起不到作用,此時需要使用如下的函數來向接口發起請求及驗證結果。
get/post/put/patch/delete('address',[payloads]) //以以上的HTTP方法請求路由
seeJson([json內容]) //檢查返回的json是否包含內容
當然之前的visit
方法可以看作沒有額外數據的get
(注意不是無參數,后文會介紹)
一個例子如下,這部分代碼通過getTable函數驗證了獲取html表格內容的正確性。
$_GET['id'] = $this->report_id_pub ;
$html_file = Config::get('phylab.experimentViewPath').$this->report_id_pub.".html";
$str_html = file_get_contents($html_file);
$this->visit('/getTable')
->seeJson([
'status' => SUCCESS_MESSAGE ,
'contents' => $str_html ,
]) ;
此外,還有call('http請求方法','路由',‘數據’...)
這一方法以更靈活的方式向接口發起請求。這一函數將返回Laravel原生的http請求對象。你可以繼而通過phpunit的斷言驗證其中結果。
Laravel增加了一些額外的斷言以方便用戶檢查Web請求的結果。例如:
->assertResponseOk(); //檢查返回是否為HTTP 200
->assertResponseStatus($code); //檢查返回是否是指定的狀態碼
->assertRedirectedTo($uri, $with = []); //檢查是否有重定向
具體可以參考[PHPUnit Assertions]([https://laravel.com/docs/5.1/testing#PHPUnit Assertions](https://laravel.com/docs/5.1/testing#PHPUnit Assertions))
3.3 一些問題
1. 中間件
在Laravel中部分路由通過中間件來確保安全性,例如很多API以Auth中間件來確保只有登陸用戶才能使用接口,但在測試中經常登陸無疑增加了測試的復雜性,使用session相對來說也是一種麻煩的辦法。
可以在測試類的開頭增加如下內容
use WithoutMiddleware;
從而在該測試代碼文件中暫時使中間件不產生作用
2. 數據庫
部分測試的函數需要對數據庫進行操作,而測試前后需要保證數據庫狀態的一致性。手動進行恢復的操作也不現實,好在Laravel內置了一些方法使我們能方便地進行數據庫方面的測試
使用遷移:use DatabaseMigrations;
或使用事務:use DatabaseTransactions;
將以上內容任一個添加在測試類開頭,Laravel會替你完成數據庫在測試前后的恢復工作
3. 關於define的問題
在我們的項目中,之前的開發者在很多地方定義了一些全局變量,或者可以說是類似於C語言的宏定義。包括Laravel框架本身中也包含了很多這樣的宏定義。初次運行測試時我們遇到的一個很尷尬的問題是:不能有多個測試代碼文件,否則phpunit就會報類似於”重復定義“的錯誤。
一個暫時的解決辦法是這樣的,在每個測試類開頭添加:
protected $preserveGlobalState = FALSE;
protected $runTestInSeparateProcess = TRUE;
這兩者意味着每個測試進程是獨立的,且各測試之間的狀態不被保留。
4. 關於HTTP請求的參數和載荷
前述的一些用於測試的請求方法,其中可以額外附帶的數組參數實際上是請求附帶的payloads,但很多時候我們要發起類似這樣的帶參數請求:
http://ip:port/getTable?id=1234567
如果直接在get
方法內設置['id'=>'1234567']
,會發現測試運行中還是直接請求/getTable
,而沒有任何參數
一個解決辦法是測試時直接修改PHP的_GET
變量,例如
$_GET['id'] = '1234567' ;
這一辦法筆者認為有些簡單粗暴,但還沒找到更好的方法,還請各位讀者多多指教。
運行測試與查看結果
1. 運行測試
運行測試非常簡單,只需要在phpunit.xml所在的目錄下運行:phpunit
,即可自動運行所有測試。
如果你想運行單個測試文件,可以以如下方式:
phpunit path\to\testFile.php
這將運行該文件中的所有測試函數。
你也可以運行某一個測試函數,但該情況下最好指定該函數所在的文件,以避免重名的情況:
phpunit --filter testFunction path\to\testFile.php
2. 查看結果
在配置部分配置了軟鏈接后,你可以直接進入測試報告的頁面
ln -s tests/codeCoverage public/tests
直接訪問ip:port/tests后將自動進入測試報告的主頁,點擊每個文件夾可以瀏覽其中內容的測試覆蓋率。
測試覆蓋率分為三欄,第一欄是覆蓋的行數,第二欄是方法數,第三欄是覆蓋的類的數量。