鍍金池/ 教程/ PHP/ 測試
門面
Laravel Homestead
安裝及配置
測試
HTTP 中間件
加密
升級指南
幫助函數(shù)
應(yīng)用目錄結(jié)構(gòu)
集合
新手入門指南-簡單任務(wù)管理系統(tǒng)
任務(wù)調(diào)度
查詢構(gòu)建器
視圖
驗(yàn)證
Laravel Cashier(訂購&支付&發(fā)票)
本地化
隊(duì)列
調(diào)整器
分頁
文件系統(tǒng)/云存儲(chǔ)
貢獻(xiàn)代碼
哈希
HTTP 控制器
緩存
遷移
HTTP 請求
Laravel Elixir
發(fā)行版本說明
Envoy 任務(wù)運(yùn)行器(SSH任務(wù))
序列化
Session
起步
帶用戶功能的任務(wù)管理系統(tǒng)
起步
用戶授權(quán)
郵件
事件
填充數(shù)據(jù)
HTTP 路由
服務(wù)提供者
Blade 模板引擎
包開發(fā)
用戶認(rèn)證
Artisan 控制臺
HTTP 響應(yīng)
集合
服務(wù)容器
關(guān)聯(lián)關(guān)系
一次請求的生命周期
契約
Redis
錯(cuò)誤&日志

測試

1、簡介

Laravel 植根于測試,實(shí)際上,內(nèi)置使用 PHPUnit 對測試提供支持是即開即用的,并且 phpunit.xml 文件已經(jīng)為應(yīng)用設(shè)置好了??蚣苓€提供了方便的幫助方法允許你對應(yīng)用進(jìn)行富有表現(xiàn)力的測試。

tests 目錄中提供了一個(gè) ExampleTest.php 文件,安裝完新的 Laravel 應(yīng)用后,只需簡單在命令行運(yùn)行 phpunit來運(yùn)行測試。

1.1 測試環(huán)境

運(yùn)行測試的時(shí)候,Laravel 自動(dòng)設(shè)置配置環(huán)境為testing。Laravel 在測試時(shí)自動(dòng)配置 session 和 cache 驅(qū)動(dòng)為數(shù)組驅(qū)動(dòng),這意味著測試時(shí)不會(huì)持久化存儲(chǔ) session 和 cache。 如果需要的話你可以自由創(chuàng)建其它測試環(huán)境配置。testing 環(huán)境變量可以在 phpunit.xml文件中配置。

1.2 定義&運(yùn)行測試

要?jiǎng)?chuàng)建一個(gè)測試用例,只需簡單在 tests 目錄創(chuàng)建一個(gè)新的測試文件,測試類應(yīng)該繼承 TestCase,然后你可以使用 PHPUnit 定義測試方法。要運(yùn)行測試,簡單從終端執(zhí)行 phpunit 命令即可:

<?php

class FooTest extends TestCase{
    public function testSomethingIsTrue()
    {
        $this->assertTrue(true);
    }
}

注意:如果你在測試類中定義自己的 setUp方法,確保在其中調(diào)用 parent::setUp

2、應(yīng)用測試

Laravel 為生成 HTTP 請求、測試輸出、以及填充表單提供了平滑的 API。舉個(gè)例子,我們看下tests目錄下包含的ExampleTest.php 文件:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase{
    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5')
             ->dontSee('Rails');
    }
}

visit 方法生成了一個(gè) GET 請求,see 方法對我們從應(yīng)用返回響應(yīng)中應(yīng)該看到的給定文本進(jìn)行斷言。dontSee 方法對給定文本沒有從應(yīng)用響應(yīng)中返回進(jìn)行斷言。在 Laravel 中這是最基本的有效應(yīng)用測試。

2.1 與應(yīng)用交互

當(dāng)然,除了對響應(yīng)文本進(jìn)行斷言之外還有做更多測試,讓我們看一些點(diǎn)擊鏈接和填充表單的例子:

2.1.1 點(diǎn)擊鏈接

在本測試中,我們將為應(yīng)用生成請求,在返回的響應(yīng)中“點(diǎn)擊”鏈接,然后對訪問 URI 進(jìn)行斷言。例如,假定響應(yīng)中有一個(gè)“關(guān)于我們”的鏈接:

<a href="/about-us">About Us</a>

現(xiàn)在,讓我們編寫一個(gè)測試點(diǎn)擊鏈接并斷言用戶訪問頁面是否正確:

public function testBasicExample(){
    $this->visit('/')
         ->click('About Us')
         ->seePageIs('/about-us');
}

2.1.2 處理表單

Laravel 還為處理表單提供了多個(gè)方法。type, select, check, attach, 和 press 方法允許你與所有表單輸入進(jìn)行交互。例如,我們假設(shè)這個(gè)表單存在于應(yīng)用注冊頁面:

<form action="/register" method="POST">
    {!! csrf_field() !!}

    <div>
        Name: <input type="text" name="name">
    </div>

    <div>
        <input type="checkbox" value="yes" name="terms"> Accept Terms
    </div>

    <div>
        <input type="submit" value="Register">
    </div>
</form>

我們可以編寫測試完成表單并檢查結(jié)果:

public function testNewUserRegistration(){
    $this->visit('/register')
         ->type('Taylor', 'name')
         ->check('terms')
         ->press('Register')
         ->seePageIs('/dashboard');
}

當(dāng)然,如果你的表單包含其他輸入比如單選按鈕或下拉列表,也可以輕松填寫這些字段類型。這里是所有表單操作方法列表:

http://wiki.jikexueyuan.com/project/laravel-5.1/images/10.png" alt="" />

2.1.3 處理附件

如果表單包含 file輸入類型,可以使用attach 方法添加文件到表單:

public function testPhotoCanBeUploaded(){
    $this->visit('/upload')
         ->name('File Name', 'name')
         ->attach($absolutePathToFile, 'photo')
         ->press('Upload')
         ->see('Upload Successful!');
}

2.2 測試JSON API

Laravel 還提供多個(gè)幫助函數(shù)用于測試 JSON API 及其響應(yīng)。例如,get, post, put, patch, 和 delete 方法用于通過多種 HTTP 請求方式發(fā)出請求。你還可以輕松傳遞數(shù)據(jù)和頭到這些方法。作為開始,我們編寫測試來生成 POST 請求到/user 并斷言返回的數(shù)據(jù)是否是 JSON 格式:

<?php

class ExampleTest extends TestCase{
    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->post('/user', ['name' => 'Sally'])
             ->seeJson([
                 'created' => true,
             ]);
    }
}

seeJson 方法將給定數(shù)組轉(zhuǎn)化為 JSON,然后驗(yàn)證應(yīng)用返回的整個(gè) JSON 響應(yīng)中的 JSON 片段。因此,如果在 JSON 響應(yīng)中有其他屬性,只要給定片段存在的話測試依然會(huì)通過。

2.2.1 精確驗(yàn)證 JSON 匹配

如果你想要驗(yàn)證給定數(shù)組和應(yīng)用返回的 JSON 能夠精確匹配,使用 seeJsonEquals 方法:

<?php

class ExampleTest extends TestCase{
    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->post('/user', ['name' => 'Sally'])
             ->seeJsonEquals([
                 'created' => true,
             ]);
    }
}

2.3 Session/認(rèn)證

Laravel 提供個(gè)多個(gè)幫助函數(shù)在測試期間處理 session,首先,可以使用 withSession 方法設(shè)置 session 值到給定數(shù)組。這通常在測試請求前獲取 session 數(shù)據(jù)時(shí)很有用:

<?php

class ExampleTest extends TestCase{
    public function testApplication()
    {
        $this->withSession(['foo' => 'bar'])
             ->visit('/');
    }
}

當(dāng)然,session 的通常用于操作用戶狀態(tài),例如認(rèn)證用戶。幫助函數(shù)actingAs 提供了認(rèn)證給定用戶為當(dāng)前用戶的簡單方法,例如,我們使用模型工廠生成和認(rèn)證用戶:

<?php

class ExampleTest extends TestCase{
    public function testApplication()
    {
        $user = factory('App\User')->create();

        $this->actingAs($user)
             ->withSession(['foo' => 'bar'])
             ->visit('/')
             ->see('Hello, '.$user->name);
    }
}

2.4 禁止中間件

測試應(yīng)用時(shí),為某些測試禁止中間件很方便。這種機(jī)制允許你將路由和控制器與中間件孤立開來做測試,Laravel 包含了一個(gè)簡單的 WithoutMiddleware trait,可以使用該 trait 自動(dòng)在測試類中禁止所有中間件:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase{
    use WithoutMiddleware;
    //
}

如果你只想在某些方法中禁止中間件,可以在測試方法中調(diào)用 withoutMiddleware方法:

<?php

class ExampleTest extends TestCase{
    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->withoutMiddleware();

        $this->visit('/')
             ->see('Laravel 5');
    }
}

2.5 自定義 HTTP 請求

如果你想要在應(yīng)用中生成自定義 HTTP 請求并獲取完整的 Illuminate\Http\Response對象,可以使用 call方法:

public function testApplication(){
    $response = $this->call('GET', '/');
    $this->assertEquals(200, $response->status());
}

如果你要生成 POST, PUT, 或者 PATCH 請求可以在請求中傳入輸入數(shù)據(jù)數(shù)組,在路由或控制器中可以通過 Request 實(shí)例訪問請求數(shù)據(jù):

$response = $this->call('POST', '/user', ['name' => 'Taylor']);

3、處理數(shù)據(jù)庫

Laravel 還提供了多種有用的工具讓測試數(shù)據(jù)庫驅(qū)動(dòng)的應(yīng)用更加簡單。首先,你可以使用幫助函數(shù) seeInDatabase 來斷言數(shù)據(jù)庫中的數(shù)據(jù)是否和給定數(shù)據(jù)集合匹配。例如,如果你想要通過 email 值為 sally@example.com 的條件去數(shù)據(jù)表users 查詢是否存在該記錄 ,我們可以這樣做:

public function testDatabase(){
    // 調(diào)用應(yīng)用...
    $this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}

3.1 每次測試后重置數(shù)據(jù)庫

每次測試后重置數(shù)據(jù)庫通常很有用,這樣的話上次測試的數(shù)據(jù)不會(huì)影響下一次測試。

3.1.1 使用遷移

一種方式是每次測試后回滾數(shù)據(jù)庫并在下次測試前重新遷移。Laravel 提供了一個(gè)簡單的 DatabaseMigrationstrait 來自動(dòng)為你處理。在測試類上簡單使用該 trait 如下:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase{
    use DatabaseMigrations;

    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}

3.1.2 使用事務(wù)

另一種方式是將每一個(gè)測試用例包裹到一個(gè)數(shù)據(jù)庫事務(wù)中,Laravel 提供了方便的 DatabaseTransactionstrait 自動(dòng)為你處理:

<?php

use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase{
    use DatabaseTransactions;

    /**
     * 基本功能測試示例
     *
     * @return void
     */
    public function testBasicExample()
    {
        $this->visit('/')
             ->see('Laravel 5');
    }
}

3.2 模型工廠

測試時(shí),通常需要在執(zhí)行測試前插入新數(shù)據(jù)到數(shù)據(jù)庫。在創(chuàng)建測試數(shù)據(jù)時(shí),Laravel 允許你使用”factories”為每個(gè)Eloquent 模型定義默認(rèn)的屬性值集合,而不用手動(dòng)為每一列指定值。作為開始,我們看一下 atabase/factories/ModelFactory.php 文件,該文件包含了一個(gè)工廠定義:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
});

在閉包中,作為工廠定義,我們返回該模型上所有屬性默認(rèn)測試值。該閉包接收 PHP 庫 Faker 實(shí)例,從而允許你方便地為測試生成多種類型的隨機(jī)數(shù)據(jù)。 當(dāng)然,你可以添加更多工廠到 ModelFactory.php 文件。

3.2.1 多個(gè)工廠類型

有時(shí)候你可能想要為同一個(gè) Eloquent 模型類生成多個(gè)工廠,例如,除了正常用戶外可能你想要為“管理員”用戶生成一個(gè)工廠,你可以使用 defineAs方法定義這些工廠:

$factory->defineAs(App\User::class, 'admin', function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => str_random(10),
        'remember_token' => str_random(10),
        'admin' => true,
    ];
});

你可以使用raw方法獲取基本屬性而不用重復(fù)基本用戶工廠中的所有屬性,獲取這些屬性后,只需將你要求的額外值增補(bǔ)進(jìn)去即可:

$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
    $user = $factory->raw(App\User::class);
    return array_merge($user, ['admin' => true]);
});

3.2.2 在測試中使用工廠

定義好工廠后,可以在測試或數(shù)據(jù)庫填充文件中通過全局的 factory方法使用它們來生成模型實(shí)例,所以,讓我們看一些生成模型的例子,首先,我們使用 make 方法,該方法創(chuàng)建模型但不將其保存到數(shù)據(jù)庫:

public function testDatabase(){
    $user = factory(App\User::class)->make();
    // 用戶模型測試...
}

如果你想要覆蓋模型的一些默認(rèn)值,可以傳遞數(shù)組值到 make 方法。只有指定值被替換,其他數(shù)據(jù)保持不變:

$user = factory(App\User::class)->make([
    'name' => 'Abigail',
]);

還可以創(chuàng)建多個(gè)模型集合或者創(chuàng)建給定類型的集合:

// 創(chuàng)建 3 個(gè) App\User 實(shí)例...
$users = factory(App\User::class, 3)->make();
// 創(chuàng)建 1 個(gè) App\User "admin" 實(shí)例...
$user = factory(App\User::class, 'admin')->make();
// 創(chuàng)建 3 個(gè) App\User "admin" 實(shí)例...
$users = factory(App\User::class, 'admin', 3)->make();

3.2.3 持久化工廠模型

create方法不僅能創(chuàng)建模型實(shí)例,還可以使用 Eloquent 的save方法將它們保存到數(shù)據(jù)庫:

public function testDatabase(){
    $user = factory(App\User::class)->create();
    //用戶模型測試...
}

你仍然可以通過傳遞數(shù)組到 create 方法覆蓋模型上的屬性:

$user = factory(App\User::class)->create([
    'name' => 'Abigail',
]);

3.2.4 添加關(guān)聯(lián)關(guān)系到模型

你甚至可以持久化多個(gè)模型到數(shù)據(jù)庫。在本例中,我們添加一個(gè)關(guān)聯(lián)到創(chuàng)建的模型,使用 create 方法創(chuàng)建多個(gè)模型的時(shí)候,會(huì)返回一個(gè) Eloquent 集合實(shí)例,從而允許你使用集合提供的所有便利方法,例如 each

$users = factory(App\User::class, 3)
           ->create()
           ->each(function($u) {
                $u->posts()->save(factory(App\Post::class)->make());
            });

4、模擬

4.1 模擬事件

如果你在重度使用 Laravel 的時(shí)間系統(tǒng),可能想要在測試時(shí)模擬特定事件。例如,如果你在測試用戶注冊,你可能不想所有 UserRegistered 的時(shí)間處理器都被觸發(fā),因?yàn)檫@可能會(huì)發(fā)送歡迎郵件,等等。

Laravel 提供可一個(gè)方便的 expectsEvents 方法來驗(yàn)證期望的事件被觸發(fā),但同時(shí)阻止該事件的其它處理器運(yùn)行:

<?php

class ExampleTest extends TestCase{
    public function testUserRegistration()
    {
        $this->expectsEvents(App\Events\UserRegistered::class);
        // 測試用戶注冊代碼...
    }
}

如果你想要阻止所有事件運(yùn)行,可以使用 withoutEvents 方法:

<?php

class ExampleTest extends TestCase{
    public function testUserRegistration()
    {
        $this->withoutEvents();
        // 測試用戶注冊代碼...
    }
}

4.2 模擬隊(duì)列任務(wù)

有時(shí)候,你可能想要在請求時(shí)簡單測試控制器分發(fā)的指定任務(wù),這允許你孤立的測試路由/控制器——將其從任務(wù)邏輯中分離出去,當(dāng)然,接下來你可以在一個(gè)獨(dú)立測試類中測試任務(wù)本身。 Laravel 提供了一個(gè)方便的 expectsJobs 方法來驗(yàn)證期望的任務(wù)被分發(fā),但該任務(wù)本身不會(huì)被測試:

<?php

class ExampleTest extends TestCase{
    public function testPurchasePodcast()
    {
        $this->expectsJobs(App\Jobs\PurchasePodcast::class);
        // 測試購買播客代碼...
    }
}

注意:這個(gè)方法只檢查通過 DispatchesJobs trait 分發(fā)方法分發(fā)的任務(wù),并不檢查直接通過 Queue::push分發(fā)的任務(wù)。

4.3 模擬門面

測試的時(shí)候,你可能經(jīng)常想要模擬 Laravel 門面的調(diào)用,例如,看看下面的控制器動(dòng)作:

<?php

namespace App\Http\Controllers;

use Cache;
use Illuminate\Routing\Controller;

class UserController extends Controller{
    /**
     * 顯示應(yīng)用用戶列表
     *
     * @return Response
     */
    public function index()
    {
        $value = Cache::get('key');

        //
    }
}

我們可以通過使用 shouldReceive 方法模擬 Cache 門面的調(diào)用,該方法返回一個(gè) Mockery 模擬的實(shí)例,由于門面通過 Laravel 服務(wù)容器解析和管理,它們比通常的靜態(tài)類更具有可測試性。例如,我們來模擬 Cache 門面的調(diào)用:

<?php

class FooTest extends TestCase{
    public function testGetIndex()
    {
        Cache::shouldReceive('get')
                    ->once()
                    ->with('key')
                    ->andReturn('value');

        $this->visit('/users')->see('value');
    }
}

注意:不要模擬 Request 門面,取而代之地,在測試時(shí)傳遞輸入到 HTTP 幫助函數(shù)如 callpost