## **簡介**
在現代軟件開發流程中,測試驅動開發、持續交付、持續集成這些概念中都將測試作為開發流程的有機組成部分,要求我們在軟件開發的一開始,就要設計好相關的測試方法,從而讓代碼更加易于擴展、迭代和維護。
說到測試,常見的測試主要包括單元測試和功能測試。
* 單元測試是一種通過編寫測試代碼來確認函數、類和方法是否以我們預期的方式來工作,單元測試會貫穿整個項目的開發周期。通過檢查各個函數和方法的輸入輸出,就可以保證代碼內部的邏輯已經正確執行,PHPUnit 就是最著名的單元測試框架。
* 功能測試是通過使用工具來生成自動化的測試用例,然后在真實的系統上運行,而不是單元測試中簡單的驗證單個模塊的正確性。這些工具會使用有代表性的真實數據來模擬真實用戶的行為從而驗證系統的正確性,常見的測試工具有[Selenium](https://www.seleniumhq.org/),用于瀏覽器功能測試的[Laravel Dusk](https://xueyuanjun.com/post/19543.html)就是基于 Selenium 實現的。
我們先來介紹單元測試。
## **PHPUnit簡介和安裝配置**
在 PHP 語言中,最著名的單元測試框架就是 PHPUnit 了,下面我們將以 PHPUnit 為例,演示如何在 PHP 項目中進行單元測試。
PHPUnit 目前有很多支持的版本,并且隨著 PHP 版本的不同,功能也不盡相同,在選擇版本時要注意與系統 PHP 版本的兼容性:
| 主版本 | PHP版本兼容性 | 發布時間 | 支持期限 |
| --- | --- | --- | --- |
| PHPUnit 8 | PHP 7.2、7.3、7.4 | 2019年2月1日 | 2021年2月5日 |
| PHPUnit 7 | PHP 7.1、7.2、7.3 | 2018年2月2日 | 2020年2月7日 |
| PHPUnit 6 | PHP 7.0、7.1、7.2 | 2017年2月3日 | 2019年2月1日 |
| PHPUnit 5 | PHP 5.6、7.0、7.1 | 2015年10月2日 | 2018年2月2日 |
版本最新的是 PHPUnit 8,Laravel 5.8 目前底層默認使用的還是 PHPUnit 7,但你可以選擇升級到 PHPUnit 8。關于各個版本的功能差異可以在[官方文檔](https://phpunit.de/)上查看。
在 Laravel 項目中,PHPUnit 已經開箱支持了,如果是在其它項目中使用,建議通過 Composer 進行安裝:
~~~
composer require --dev phpunit/phpunit ^7
//由于不會在線上環境進行測試,所以加上了`--dev`選項表示僅在本地安裝
~~~
這里我們以自己新建的空項目為例,首先通過`composer init`命令初始化`composer.json`文件,然后通過上述命令安裝 PHPUnit。
接下來,在項目目錄下創建一個新的子目錄來存放測試代碼,仿照 Laravel 框架將其命名為`tests`,接著在`tests`目錄下創建子目錄`Unit`用于存放單元測試代碼(Laravel的`tests`目錄中包含的`Unit`和`Feature`子目錄下存放的測試用例分別用于單元測試和功能測試,二者都是基于 PHPUnit 實現,對應的測試用例的根類都是`PHPUnit\Framework\TestCase`)。
然后在項目根目錄下創建一個`phpunit.xml`文件用于編排和初始化 PHPUnit 的測試行為,PHPUnit 在執行測試之前會基于這個文件進行初始化設置,你可以將其看作是 PHPUnit 的配置文件,下面我們就從這個文件為入口,分析 Laravel 框架如何集成 PHPUnit 進行單元測試和功能測試。
Laravel 框架已經為我們做好了如下初始化設置:
~~~
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
</whitelist>
</filter>
<php>
<server name="APP_ENV" value="testing"/>
<server name="BCRYPT_ROUNDS" value="4"/>
<server name="CACHE_DRIVER" value="array"/>
<server name="MAIL_DRIVER" value="array"/>
<server name="QUEUE_CONNECTION" value="sync"/>
<server name="SESSION_DRIVER" value="array"/>
</php>
</phpunit>
~~~
該文件的第一行是 XML 文件的版本和編碼描述信息,從第二行開始的`<phpunit>`元素則正式開始配置 PHPUnit 的核心功能,在該元素里面還嵌套定義了其它子元素,用于配置測試套件、過濾器、PHP 變量等其它信息。
### **通用配置**
`phpunit`元素上的屬性屬 通用配置,很多都可以在執行`phpunit`命令時通過命令行參數的形式傳入,但是如果參數太多,且每次傳入參數都是一樣的,顯然配置到`phpunit.xml`中更方便,也更加易于維護,可通過`phpunit --help`在命令行查看:
* `backupGlobals`屬性對應命令行參數里的`--globals-backup`,用于在每個測試中備份和恢復 PHP 超全局變量`$GLOBALS`,這里設置為`false`表示不做相應的備份和恢復操作;
* `backupStaticAttributes`屬性對應命令行參數里的`static-backup`,用于在每個測試中備份和恢復靜態屬性,這里設置為`false`表示不做相應的備份和恢復操作;
* `bootstrap`屬性對應命令行參數里面的`--bootstrap <file>`,用于指定測試運行前需要引入的文件,這里配置為`vendor/autoload.php`表示會引入 Composer 自動加載和管理的所有依賴,以便在測試文件中使用;
* `colors`屬性對應命令行參數里的`--colors=<flag>`,用于指示在輸出中是否用顏色進行標識;
* `processIsolation`屬性對應命令行參數里的`--process-isolation`,用于表示是否在隔離的 PHP 進程中執行測試;
* `stopOnFailure`屬性對應命令行參數里的`--stop-on-failure`,用于表示測試出錯或失敗時是否退出腳本執行,配置為`false`表示不退出;
接下來是一些不能通過命令行參數指定的屬性:
* `convertErrorsToExceptions`屬性用于定義是否將 PHP ERROR 級別錯誤轉化為異常,默認會轉化為異常的錯誤類型包括:`E_WARNING`、`E_NOTICE`、`E_USER_ERROR`、`E_USER_WARNING`、`E_USER_NOTICE`、`E_STRICT`、`E_RECOVERABLE_ERROR`、`E_DEPRECATED`、`E_USER_DEPRECATED`,這里將該屬性設置為`true`表示啟用該功能。
* `convertNoticesToExceptions`屬性用于定義是否將 PHP NOTICE 級別錯誤轉化為異常,設置為`true`表示會將`E_NOTICE`、`E_USER_NOTICE`、`E_STRICT`三種級別錯誤轉化為異常。
* `convertWarningsToExceptions`屬性用于定義是否將 PHP WARNING 級別錯誤轉化為異常,設置為`true`表示會將`E_WARNING`或`E_USER_WARNING`級別錯誤轉化為異常。
當然,這里只包含了 PHPUnit 所支持的`phpunit`配置的一部分屬性,更多配置請參考[官方文檔](https://phpunit.readthedocs.io/zh_CN/latest/configuration.html#phpunit)及[PHPUnit 命令行參數配置](https://phpunit.readthedocs.io/en/8.0/textui.html#textui-clioptions)。
### **測試套件**
它們定義在子元素`<testsuites>`中,你可以像 Laravel 框架這樣通過`<testsuites>`配置單個/多個`<testsuite>`,這取決于項目的復雜度或者你的需求。
Laravel 框架默認通過`<testsuites>`定義了兩個`<testsuite>`,分別是用于單元測試的`Unit`和用于功能的測試的`Feature`,在它們各自的測試套件中,通過`directory`子元素指定對應測試文件所在的目錄,并通過`suffix`屬性指定測試文件的文件名后綴,這樣,當運行`phpunit`命令時,PHPUnit 會從指定目錄下匹配指定后綴的測試文件進行測試。
在運行`phpunit`命令時,我們可以通過相應測試套件的名稱匹配要執行的測試用例:
~~~
./vendor/bin/phpunit --testsuite=Unit
~~~
更多測試套件的配置選項可以參考[官方文檔](https://phpunit.readthedocs.io/zh_CN/latest/configuration.html#appendixes-configuration-testsuites)。
### **過濾器**
Laravel 框架還通過`<filter>`元素配置了過濾器,在該元素中我們可以通過`whitelist`子元素指定用于配置代碼覆蓋率報告分析所使用的白名單,代碼覆蓋率是代碼測試中一個很重要的概念,我們的測試代碼要盡可能覆蓋到 100% 的業務代碼,這樣的測試才有意義,而 Laravel 應用代碼都位于項目根目錄下的`app`目錄中,并且我們只測試 PHP 代碼,所以在`<whitelist>`中通過`directory`子元素做了相應的配置。
這樣,我們在運行`phpunit`時加上`--coverage-html .`參數,就可以在根目錄下生成 HTML 格式的測試覆蓋率報告文檔了:

### **PHP變量**
Laravel 框架還通過`<php>`元素為我們初始化了一些 PHPUnit 測試環境下的 PHP 常量,上例中配置相當于以下PHP代碼:
~~~
$_SERVER['APP_ENV'] = 'testing';
$_SERVER['BCRYPT_ROUNDS'] = '4';
$_SERVER['CACHE_DRIVER'] = 'array';
$_SERVER['MAIL_DRIVER'] = 'array';
$_SERVER['QUEUE_CONNECTION'] = 'sync';
$_SERVER['SESSION_DRIVER'] = 'array';
~~~
通過上述配置我們可以得知,在 Laravel 測試環境下,`APP_ENV`的值是`testing`,因此,我們可以在根目錄下創建一個`.env.testing`文件作為測試環境下的環境配置文件,運行`phpunit`時實際執行的是控制臺應用的 Kernel 來啟動應用,這樣,系統就會通過`.env.testing`讀取環境配置。
緩存、郵件、會話驅動都是通過數組模擬,因而不會持久化到硬盤,此外隊列驅動是`sync`,表示會同步執行推送到隊列的任務。
除此之外,還可以初始化 PHP 請求、常量、INI 設置、Cookie、超全局變量等信息,更多使用明細請參考[官方文檔](https://phpunit.readthedocs.io/zh_CN/latest/configuration.html#php-ini)。
## **編寫第一個PHPUnit測試用例**
在編寫測試用例之前,我們需要了解關于編寫單元測試的一些常見約定:
* 測試文件名需要以`Test`作為后綴,比如如果要測試`First.php`,則對應的測試文件名為`FirstTest.php`;
* 測試方法名需要以`test`作為前綴,比如如果要測試的方法名為`getuser`,則對應的測試方法名為`testGetuser`,此外,你還可以通過`@test`注解來聲明一個測試方法;
* 所有的測試方法可見性必須是`public`;
* 所有的測試類都繼承自`PHPUnit\Framework\TestCase`。
### **基于PHPUnit編寫單元測試**
了解了這些約定之后,就可以編寫第一個單元測試用例了——郵件測試用例。
首先我們來編寫一個應用類`Email`:如果當前是空項目,在項目根目錄下創建一個`app`目錄,并將`Email.php`保存在該目錄下,然后編寫`Email`類代碼如下:
~~~
<?php
namespace App;
final class Email
{
private $email;
private function __construct(string $email)
{
$this->isValidEmail($email);
$this->email = $email;
}
public static function fromString(string $email): self
{
return new self($email);
}
public function __toString(): string
{
return $this->email;
}
private function isValidEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException(
sprintf(
'"%s" is not a valid email address',
$email
));
}
}
}
~~~
然后,我們在`composer.json`中配置`autoload`選項通過 PSR-4 規則加載`app`目錄下的類文件:
~~~
"autoload": {
"psr-4": {
"App\\": "app/"
}
},
~~~
以便能夠通過命名空間加載到`app`目錄下的類。配置完成后,運行`composer dump-auto`命令重新生成自動加載文件以便`Email`類可以被正常加載到。
接下來,我們在`tests/Unit`目錄下創建一個新的測試用例`EmailTest.php`,用來測試剛剛編寫的`Email`類:
~~~
<?php
namespace Unit\Test;
use PHPUnit\Framework\TestCase;
use App\Email;
class EmailTest extends TestCase
{
public function testCanBeCreatedFromValidEmailAddress(): void
{
$this->assertInstanceOf(
Email::class,
Email::fromString('user@example.com')
);
}
public function testCannotBeCreatedFromInvalidEmailAddress(): void
{
$this->expectException(\InvalidArgumentException::class);
Email::fromString('invalid');
}
public function testCanBeUsedAsString(): void
{
$this->assertEquals(
'user@example.com',
Email::fromString('user@example.com')
);
}
}
~~~
在上述代碼中,第一個測試方法使用了基類提供的斷言方法`assertInstanceOf`判斷`Email::fromString`方法返回的是否是`Email`實例;第二個測試方法中使用了`expectException`判斷傳入無效的郵件地址是否拋出指定異常;第三個測試方法則使用`assertEquals`判斷`Email::fromString`打印結果是否與給定字符串相等。
#### **對變量進行測試**
PHPUnit 底層提供了很多斷言方法用于對變量進行測試,這些變量通常是業務代碼類方法或函數的返回值,我們在`Unit\ExampleTest`中新增一個`testVariables`方法:
~~~
public function testVariables()
{
$bool = false;
$number = 100;
$arr = ['Laravel', 'PHP', '學院君'];
$obj = null;
// 斷言變量值是否為假,與 assertTrue 相對
$this->assertFalse($bool);
// 斷言給定變量值是否與期望值相等,與 assertNotEquals 相對
$this->assertEquals(100, $number);
// 斷言數組中是否包含給定值,與 assertNotContains 相對
$this->assertContains('學院君', $arr);
// 斷言數組長度是否與期望值相等,與 assertNotCount 相對
$this->assertCount(3, $arr);
// 斷言數組是否不會空,與 assertEmpty 相對
$this->assertNotEmpty($arr);
// 斷言變量是否為 null,與 assertNotNull 相對
$this->assertNull($obj);
}
~~~
相應的斷言用途在注釋中已經說明了,我們可以對各種類型的變量從各種維度進行斷言,甚至還可以對文件、目錄、正則表達式進行斷言,并且很多斷言都可以從正反兩個方法進行,相關的調用都很簡單,你可以在需要的時候查看官方文檔選擇相應的斷言方法:[https://phpunit.readthedocs.io/zh\_CN/latest/assertions.html](https://phpunit.readthedocs.io/zh_CN/latest/assertions.html)。
#### **對輸出進行測試**
通過 PHPUnit 提供的`expectOutputString`方法來對頁面輸出進行測試:
~~~
public function testOutput()
{
$this->expectOutputString('學院君');
$this->expectOutputRegex('/Laravel/i');
echo '學院君';
}
~~~
#### **對異常進行測試**
類似的,還可以通過`expectException`方法對異常進行測試,為了讓測試用例更加符合真實場景,我們在`app`目錄下新增一個`Services`子目錄,然后在該子目錄下創建一個`TestService`類并初始化代碼如下:
~~~
<?php
namespace App\Services;
class TestService
{
public function invalidArgument()
{
throw new \InvalidArgumentException('無效的參數');
}
}
~~~
然后回到`Unit\ExampleTest`,編寫一個新的測試用例如下:
~~~
public function testException()
{
$this->expectException(\InvalidArgumentException::class);
$service = new TestService();
$service->invalidArgument();
}
~~~
除此之外,還可以進一步對異常明細進行測試,比如通過`expectExceptionCode()`、`expectExceptionMessage()`和`expectExceptionMessageRegExp()`方法可以用于測試異常碼、異常信息。
除了通過上述方法,還可以通過注解對異常進行測試,這種方式更加方便:
~~~
/**
* @expectedException \InvalidArgumentException
*/
public function testExceptionAnnotation()
{
$this->service->invalidArgument();
}
~~~
#### **測試的依賴關系**
有的時候,我們需要測試的兩個用例之間可能有依賴關系,比如我們在`TestService`定義如下個方法:
~~~
protected $stack = [];
public function init()
{
$this->stack = ['學院君', 'Laravel學院', '單元測試'];
}
public function stackContains($value)
{
return in_array($value, $this->stack);
}
public function getStackSize()
{
return count($this->stack);
}
~~~
我們在測試`stackContains`方法時,往往要先調用`init`方法,但是在單元測試中,每個方法都有獨立的測試用例,如果多次調用有可能會對數據造成污染,那我們能否在`init`方法測試用例的運行基礎上運行`stackContains`方法的測試用例呢?這個時候,我們說這兩個測試用例之間是具有依賴關系的,PHPUnit 中通過`@depends`注解對這種依賴關系進行了支持,我們可以在`Unit\ExampleTest`中編寫測試用例如下:
~~~
public function testInitStack()
{
$this->service->init();
$this->assertEquals(3, $this->service->getStackSize());
return $this->service;
}
/**
* @depends testInitStack
* @param TestService $service
*/
public function testStackContains(TestService $service)
{
$contains = $service->stackContains('學院君');
$this->assertTrue($contains);
}
~~~
在`testStackContains`用例中,我們將`testInitStack`用例返回的`$service`實例傳遞進來,并在此基礎上進行測試。
#### **數據提供器**
除了支持測試用例之間的依賴之外,PHPUnit 還可以通過`@dataProvider`注解為多個測試用例提供初始化數據:
~~~
public function initDataProvider()
{
return [
['學院君'],
['Laravel學院']
];
}
/**
* @depends testInitStack
* @dataProvider initDataProvider
*/
public function testIsStackContains()
{
$arguments = func_get_args();
$service = $arguments[1];
$value = $arguments[0];
$this->assertTrue($service->stackContains($value));
}
~~~
在這個測試用例中,我們通過`initDataProvider`方法作為數據提供器,數據供給器方法必須聲明為`public`,其返回值要么是一個數組,其每個元素也是數組;要么是一個實現了`Iterator`接口的對象,在對它進行迭代時每步產生一個數組。每個數組都是測試數據集的一部分,將以它的內容作為參數來調用測試方法。
然后我們在需要用到這個數據提供器的測試用例上用`@dataProvider`注解進行聲明,在這個示例中我們迭代數據提供器數組,將其中的數據作為參數傳入`TestService`的`stackContains`方法以判斷對應值在`stack`屬性中是否存在。
### **Laravel基于PHPUnit實現功能測試**
Laravel 框架開箱為我們提供了一個功能測試用例示例`tests/Feature/ExampleTest.php`:
~~~
<?php
namespace Tests\Feature;
use Tests\TestCase;
class ExampleTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testBasicTest()
{
$response = $this->get('/');
$response->assertStatus(200);
}
}
~~~
功能測試類都位于`tests/Feature`目錄下,和單元測試類一樣,也繼承自`Tests\TestCase`,從根源上都繼承自`PHPUnit\Framework\TestCase`,只不過功能測試用到的很多方法都是 Laravel 自行封裝實現的【**單元測試基本上使用的是PHPUnit框架提供的原生方法**】,這些實現都是通過獨立的 Trait 來完成,在`Illuminate\Foundation\Testing\TestCase`中,可以看到這些 Trait 的引入:
~~~
use Concerns\InteractsWithContainer,
Concerns\MakesHttpRequests,
Concerns\InteractsWithAuthentication,
Concerns\InteractsWithConsole,
Concerns\InteractsWithDatabase,
Concerns\InteractsWithExceptionHandling,
Concerns\InteractsWithSession,
Concerns\MocksApplicationServices;
~~~
比如請求相關的測試方法都位于`Illuminate\Foundation\Testing\Concerns\MakesHttpRequests`中,認證相關的測試方法都位于`Illuminate\Foundation\Testing\Concerns\InteractsWithAuthentication`中,會話相關的測試方法都位于`Illuminate\Foundation\Testing\Concerns\InteractsWithSession`中,而響應相關的測試方法都位于`Illuminate\Foundation\Testing\TestResponse`中,該實例會在調用 HTTP 功能測試類中調用`$this->get`方法時返回(當然,還支持`post`、`put`、`delete`、`getJson`等類似方法,這些方法都定義在`Illuminate\Foundation\Testing\Concerns\MakesHttpRequests`中)。
#### **基本測試**
如 Laravel 提供的示例代碼所示,我們可以從最簡單的測試開始,測試響應的狀態碼,與之等價的,我們還可以通過`assertOk`方法斷言響應狀態碼是否是 200:
~~~
public function testBasic()
{
$response = $this->get('/');
$response->assertOk(); // 返回狀態碼是否是 200
}
// `assertSuccessful`:斷言響應狀態碼是否介于200-300之間;
// `assertNotFound`:斷言響應狀態碼是否是 404;
// `assertForbidden`:斷言響應狀態碼是否是 403;
~~~
此外,我們還可以通過`assertSee`或`assertSeeText`方法斷言響應實體中是否包含給定字符串:
~~~
public function testSeeText()
{
$response = $this->get('/');
$response->assertSee('Laravel');
//區別是下者會將響應實體轉化為純文本進行判斷
$response->assertSeeText('Laravel');
}
~~~
與之相對的,還有`assertDontSee`和`assertDontSeeText`方法,與上述判斷相反,斷言響應實體中不包含給定字符串。與之類似的,還有`assertSeeInOrder`以及`assertSeeTextInOrder`方法,用于斷言給定字符串是否按照對應的順序出現在響應實體中。
#### **測試重定向**
我們可以通過`assertRedirect`對重定向響應進行測試,斷言重定向指向的 URL 是否與預期一致:
~~~
public function testRedirection()
{
$response = $this->get('/redirect');
$response->assertRedirect('https://xueyuanjun.com');
}
~~~
要測試這個重定向響應,我們需要確保在`routes/web.php`中包含如下路由定義:
~~~
Route::get('/redirect', function () {
return redirect('https://xueyuanjun.com');
});
~~~
此外,還有一個與之類似的方法`assertLocation`也可以用于斷言重定向 URL,與`assertRedirect`不同之處在于,它不會對響應狀態碼和響應頭進行判斷,`assertRedirect`會先斷言響應狀態碼是否在`[201, 301, 302, 303, 307, 308]`數組中并且響應頭中包含`Location`字段。
#### **測試響應頭**
如果你想要對響應頭進行深入測試,可以通過`assertHeader`方法實現:
~~~
public function testHeader()
{
$response = $this->get('/header');
$response->assertHeader('X-Header-One', 'Laravel學院')
->assertHeader('X-Header-Two', 'HTTP 功能測試');
}
~~~
為了讓上述測試用例通過,我們還要在`routes/web.php`中定義如下路由:
~~~
Route::get('/header', function (){
return response('測試響應頭')
->header('X-Header-One', 'Laravel學院')
->header('X-Header-Two', 'HTTP 功能測試');
});
~~~
#### **測試 Cookie**
Laravel 功能測試為 Cookie 測試提供了多個相關斷言方法。要測試響應中是否包含給定 Cookie 且與指定值匹配,可以通過`assertCookie`方法實現:
~~~
public function testCookie()
{
$response = $this->get('/cookie');
$response->assertCookie('UserName', '學院君');
}
~~~
相應的,我們在`routes/web.php`中新增如下路由:
~~~
Route::get('/cookie', function (){
return response('測試 Cookie')->cookie('UserName', '學院君');
});
// `assertCookieExpired`:斷言給定 Cookie 是否過期;
// `assertCookieNotExpired`:斷言給定 Cookie 沒有過期;
// `assertCookieMissing`:斷言給定 Cookie 不存在;
// `assertPlainCookie`:斷言給定 Cookie 存在且與給定值匹配(不加密)。
~~~
#### **測試 Session**
為了測試 HTTP Session,我們先在`routes/web.php`中定義一個新的路由:
~~~
Route::get('/session', function (){
session(['SiteName' => 'Laravel學院']);
session(['UserName' => '學院君']);
return response('測試 Session');
});
~~~
然后為這個路由編寫測試用例:
~~~
public function testSession()
{
$response = $this->get('/session');
$response->assertSessionHas('SiteName', 'Laravel學院')
->assertSessionHas('UserName')
->assertSessionMissing('AppName');
// 一次性指定包含的 Session
$response->assertSessionHasAll(['SiteName' => 'Laravel學院', 'UserName' => '學院君']);
}
~~~
我們可以通過`assertSessionHas`方法依次斷言每個 Session 存儲項,也可以通過`assertSessionHasAll`方法一次性斷言多個 Session 存儲項,在使用它們的時候,可以指定對應的 Session 值,也可以不指定,如果指定的話則必須與存儲的 Session 之匹配才會測試通過。
另外,我們可以通過`assertSessionMissing`方法斷言指定 Session 存儲項不存在。
## **運行測試用例**
我們可以通過運行`phpunit`對剛剛編寫的代碼進行測試:
~~~
./vendor/bin/phpunit
./vendor/bin/phpunit tests/Unit/EmailTest.php //指定測試用例
~~~
通過編排文件`phpunit.xml`,PHPUnit 會去`tests/Unit`目錄中查找測試用例進行測試,測試通過則顯示綠色的高亮文本,測試不通過則顯示紅色的警告文本。