服務(wù) ―― 測試

2018-02-24 15:38 更新

服務(wù) —— 測試

1、簡介

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

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

1.1 測試環(huán)境

運行測試的時候,Laravel自動設(shè)置配置環(huán)境為testing。Laravel在測試時自動配置session和cache驅(qū)動為數(shù)組驅(qū)動,這意味著測試時不會持久化存儲session和cache。

如果需要的話你可以自由創(chuàng)建其它測試環(huán)境配置。testing環(huán)境變量可以在phpunit.xml文件中配置。

1.2 定義&運行測試

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

<?php

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

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

2、應(yīng)用測試

Laravel為生成HTTP請求、測試輸出、以及填充表單提供了平滑的API。舉個例子,我們看下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方法生成了一個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)行斷言之外還有做更多測試,讓我們看一些點擊鏈接和填充表單的例子:

2.1.1 點擊鏈接

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

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

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

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

2.1.2 處理表單

Laravel還為處理表單提供了多個方法。type,?select,?check,?attach, 和press方法允許你與所有表單輸入進(jìn)行交互。例如,我們假設(shè)這個表單存在于應(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)然,如果你的表單包含其他輸入比如單選按鈕或下拉列表,也可以輕松填寫這些字段類型。這里是所有表單操作方法列表:

方法 描述
$this->type($text, $elementName) “Type” 文本到給定字段
$this->select($value, $elementName) “Select” 單選框或下拉列表
$this->check($elementName) “Check” 復(fù)選框
$this->attach($pathToFile, $elementName) “Attach” 文件到表單
$this->press($buttonTextOrElementName) “Press” ?給定文本或name的按鈕

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還提供多個幫助函數(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īng)用返回的整個JSON響應(yīng)中的JSON片段。因此,如果在JSON響應(yīng)中有其他屬性,只要給定片段存在的話測試依然會通過。

2.2.1 精確驗證JSON匹配

如果你想要驗證給定數(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提供個多個幫助函數(shù)在測試期間處理session,首先,可以使用withSession方法設(shè)置session值到給定數(shù)組。這通常在測試請求前獲取session數(shù)據(jù)時很有用:

<?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)用時,為某些測試禁止中間件很方便。這種機(jī)制允許你將路由和控制器與中間件孤立開來做測試,Laravel包含了一個簡單的WithoutMiddleware?trait,可以使用該trait自動在測試類中禁止所有中間件:

<?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ù)據(jù):

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

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

Laravel還提供了多種有用的工具讓測試數(shù)據(jù)庫驅(qū)動的應(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ù)不會影響下一次測試。

3.1.1 使用遷移

一種方式是每次測試后回滾數(shù)據(jù)庫并在下次測試前重新遷移。Laravel提供了一個簡單的DatabaseMigrations?trait來自動為你處理。在測試類上簡單使用該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ù)

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

<?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?模型工廠

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

$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實例,從而允許你方便地為測試生成多種類型的隨機(jī)數(shù)據(jù)。

當(dāng)然,你可以添加更多工廠到ModelFactory.php文件。

3.2.1 多個工廠類型

有時候你可能想要為同一個Eloquent模型類生成多個工廠,例如,除了正常用戶外可能你想要為“管理員”用戶生成一個工廠,你可以使用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方法使用它們來生成模型實例,所以,讓我們看一些生成模型的例子,首先,我們使用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)建多個模型集合或者創(chuàng)建給定類型的集合:

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

3.2.3 持久化工廠模型

create方法不僅能創(chuàng)建模型實例,還可以使用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)系到模型

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

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

4、模擬

4.1 模擬事件

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

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

<?php

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

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

<?php

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

4.2 模擬隊列任務(wù)

有時候,你可能想要在請求時簡單測試控制器分發(fā)的指定任務(wù),這允許你孤立的測試路由/控制器——將其從任務(wù)邏輯中分離出去,當(dāng)然,接下來你可以在一個獨立測試類中測試任務(wù)本身。

Laravel提供了一個方便的expectsJobs方法來驗證期望的任務(wù)被分發(fā),但該任務(wù)本身不會被測試:

<?php

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

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

4.3 模擬門面

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

<?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)用,該方法返回一個Mockery模擬的實例,由于門面通過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門面,取而代之地,在測試時傳遞輸入到HTTP幫助函數(shù)如callpost。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號