Laravel Dusk 前世今生(Laravel 5.4 新兵參上)

JokerLinly發表於2017-03-20

如果你在 Twitter 上關注過 Laravel 的開發者,或者如果你聽過 Laravel Podcast,那你就應該知道在 Laravel 應用測試中出現的新兵 —— Laravel Dusk。 

Laravel 應用測試的背景

首先,簡單回顧一下:雖然每個人在討論測試時使用的語言有點不同,但都一致認為單元測試是負責測試隔離開來的小塊的程式碼 ,例如在一個類中的一個方法。而應用測試,與整合測試類似,用來測試整個應用程式作為一個整體。

自從 Jeffrey Way 的 Integrated 成為 Laravel 5.1 核心,我們便獲得了 ->visit()->get()->see() 等方法去描述一個瀏覽器訪問網站的行動。而這確實改變了我們編寫的應用程式測試的能力,簡單地寫個例子:

/** @test */
public function cta_link_functions()
{
    $this->visit('/sales-page')
        ->click('Try it now!')
        ->see('Sign up for trial')
        ->onPage('trial-signup');
}

對後臺而言,它就等於一個 PHP 的啟動請求,通過我們的應用程式傳遞這個請求,去抓取 DOM,然後提出更多的請求,直到整條程式碼鏈完成。 從而做到在不需要瀏覽器的情況下去模擬瀏覽器的行為。

傳統測試方式帶來的問題

但如果應用程式的功能依賴了 JavaScript,那該怎麼辦?畢竟這不是一個真正的瀏覽器,難以要求它去注意到你的 JavaScript。

隨著時間的推移,在 Laravel 應用程式中使用和測試 JavaScript 元件的願望不斷增多,對於 Laravel 提供的工具無法測試大部分應用程式的不滿也越來越多。

解決方案:Laravel Dusk

Taylor 用 Dusk 完全重寫了 Laravel 應用程式測試的工作原理。而這一切全部是基於一個名為 ChromeDriver 的工具,一個實際控制著 Chrome/Chromium 的獨立的伺服器。編寫應用程式測試時,Dusk 會將命令傳送到 ChromeDriver,ChromeDriver 隨後啟動 Chrome 以在瀏覽器中執行測試,最後返回測試結果。

Laravel 所有的非應用程式測試——單元測試以及基於 HTTP 請求的測試,如 $this->get() 的程式碼的使用方法跟原來一樣。但功能更高階,如 $this->visit() ,只是你需要多做些配置。這取決於你拉入應用程式測試包。可以拉入 Dusk composer require laravel/dusk --dev 來玩,也可以拉入 5.4 之前的測試包 composer require laravel/browser-kit-testing --dev 繼續之前的測試。

注意:如果引入 Browser Kit 測試,則需要修改 TestCase 來擴充套件 Laravel\BrowserKitTesting\TestCase,而不是 Illuminate\Foundation\Testing\TestCase。 具體檢視Adam Wathan 的 為 Laravel 5.4 升級測試套件

入我 Dusk 門

一旦把 Dusk 引入到應用程式中,就需要註冊服務提供者。 你可以將它新增到 config/app.php 中的服務提供者列表。但實際上這種做法並不安全。為了方便 Dusk 進行測試,開啟了許多生產環境上不需要的設定。因此改為在 app/Providers/AppServiceProvider.php 的 register 方法中有條件地註冊會是個不錯的選擇,如下所示:

// AppServiceProvider
use Laravel\Dusk\DuskServiceProvider;

...

public function register()
{
    if ($this->app->environment('local', 'testing')) {
        $this->app->register(DuskServiceProvider::class);
    }
}

好了,現在我們要來安裝 Dusk 了,執行下面的語句之後會建立一個 tests/Browser 目錄。

php artisan dusk:install

你可能從來沒有在 .env 檔案中使用 APP_URL 鍵,因為通常對許多應用程式來說,它不是必須的,但,現在需要設定它了,因為 Dusk 依賴它來訪問應用程式。 這將是一個實際可訪問的 URL,因為我們正在使用一個真正的瀏覽器。

我們現在使用 php artisan dusk 執行測試,它可以接受 PHPUnit 可以使用的任何引數,例如,php artisan dusk --filter=the_big_button_works

寫我們第一個 Dusk 測試

像平時寫應用程式測試一樣來寫第一個 Dusk 測試,測試通過點選按鈕去到指定的位置。先執行下面的指令建立我們要用來寫測試的檔案:

php artisan dusk:make BigButtonTest

接下來開啟 tests/Browser/BigButtonTest.php,下面是預設生成的:

<?php

namespace Tests\Browser;

use Tests\DuskTestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class BigButtonTest extends DuskTestCase
{
    /**
     * A Dusk test example.
     *
     * @return void
     */
    public function testExample()
    {
        $this->browse(function ($browser) {
            $browser->visit('/')
                    ->assertSee('Laravel');
        });
    }
}

你會注意到一些不同於我們習慣的東西。

首先,這個測試檔案有名稱空間!實際上是在 5.4 無論你是否使用 Dusk,預設情況下,都會生成兩個為測試準備的目錄:Tests\UnitTests\Feature

第二,DatabaseTransactionsWithoutMiddleware 不會預設匯入。

第三,我們不再直接呼叫 $this->visit。 我們現在在 browse() 閉包函式中執行所有的測試,這個函式封裝了 Dusk。

第四,一些可用的方法已被重新命名為與其作用更一致。例如,see() 現在是 assertSee()

然後執行命令,開始測試: php artisan dusk

file

看到沒看到沒?是不是好贊!

還可以進一步試試:

$this->browse(function ($browser) {
    $browser->visit('/sales-page')
        ->clickLink('Try it now!')
        ->assertSee('Sign up for trial')
        ->assertPathIs('/trial-signup');
});

上面這些是我們曾經能夠做到的,接下來看看新的東西。

新的互動

有很多新的功能和新的互動方式,更多的東西還是看 文件 比較好些。 但是這裡有一些與過去有所不同的地方,進一步瞭解多一些也無礙。

獲取和設定表單上的值

獲取或設定 value,並獲取 text 或頁面上任何給定其 jQuery 樣式選擇器元素的屬性。

// Get or set
$inputValue = $browser->value('#name-input');
$browser->value('#email-input', 'matt@matt.com');

// Get
$welcomeDivValue = $browser->value('.welcome-text');
$buttonDataTarget = $browser->attribute('.button', 'data-target');

與表單和其他頁面元素進行互動

與表單互動非常類似於之前的情況。 首先,選擇欄位的名稱,或者一個 jQuery 樣式選擇器。

$browser->type('email', 'matt@matt.com');
$browser->type('#name-input', 'matt');

可以清除任何值:

$browser->clear('password');

可以選擇下拉選單值:

$browser->select('plan', 'premium');

可以選中核取方塊或單選按鈕:

$browser->check('agree');
$browser->uncheck('mailing-list');
$browser->radio('referred-by', 'friend');

也可以附加檔案:

$browser->attach('profile-picture', __DIR__ . '/photos/user.jpg');

甚至可以執行更復雜的基於鍵盤和滑鼠的互動:

// type 'hype' while holding the command key
$browser->keys('#magic-box', ['{command}', 'hype']);

$browser->click('#randomize');

$browser->mouseover('.hover-me');

$browser->drag('#tag__awesome', '.approved-tags');

最後,我們可以將我們的任何操作限定為我們正在處理的網站的特定形式或部分:

$browser->with('.sign-up-form', function ($form) {
    $form->type('name', 'Jim')
        ->clickLink('Go');
});

等待

這可能是 Dusk 中最對外的概念。 因為一個真正的瀏覽器,它實際上必須載入頁面上的所有外部資源,而這意味著那些資源可能還沒能那麼快載入完。

有幾個方法可以幫助你解決這個問題。 首先,您可以手動暫停測試:

// Pause for 500ms
$browser->pause(500);

或者,等待(預設情況下,最多 5 秒),直到給定的元素出現或消失:

$browser->waitFor('.chat-box');

// wait a maximum of 2 seconds for the chat box to appear
$browser->waitFor('.chat-box', 2);

$browser->waitUntilMissing('.loading');
$browser->waitForText('You have arrived!');
$browser->waitForLink('Proceed');

// wait and scope
$browser->whenAvailable('.chat-box'), function ($chatBox) {
    $chatBox->assertSee('What is your message?')
        ->type('message', 'Hello!')
        ->press('Send');
});

// wait until JavaScript expression returns true
$browser->waitUntil('App.initialized');

建立多個瀏覽器

從文件中舉一個例子,如果你想測試一個基於 websocket 對話方塊的工作情況,只需使用兩個單獨的瀏覽器進行會話:

$this->browse(function ($first, $second) {
    $first->loginAs(User::find(1))
          ->visit('/home')
          ->waitForText('Message');

    $second->loginAs(User::find(2))
           ->visit('/home')
           ->waitForText('Message')
           ->type('message', 'Hey Taylor')
           ->press('Send');

    $first->waitForText('Hey Taylor')
          ->assertSee('Jeffrey Way');
});

正如你所看到的,如果你在 browse() 閉包中請求更多的引數,每個都將被傳遞一個新的瀏覽器會話,這樣便可以與之互動。

第一個瀏覽器以使用者 1 登入,訪問主路由,然後等待(最多5秒),直到它看到 text “Message”,在此測試中,該訊息表示頁面上顯示的聊天框。 接下來,以使用者 2 登入,訪問主路由,等待檢視聊天框,然後在其中鍵入訊息並點選傳送。 最後,使用者 1 監視該訊息,並斷言使用者 2 的名字(我們假定名為 “Jeffrey Way” )顯示出來。

loginAs 是以前被命名為 be()actingAs() 的方法,可以用來取 User 例項或使用者 ID。

新斷言

Dusk 中的大多數斷言與以前相同,但是很多都有了新的名稱。 這裡有整個 列表 的內容供你檢視,而這下面列出了一些新的斷言:

$browser->assertTitle('My App - Home');
$browser->assertTitleContains('My New Blog Post');
$browser->assertVisible('.chat-box');
$browser->assertMissing('.loading');

Dusk 頁

讀取更長,更復雜的 Dusk 互動集合可能很難遵循,所以有一個可選的概念稱為頁面,使您很容易在Dusk測試中分組功能。 頁面表示可用於導航到其的URL,一組可以執行以確保瀏覽器仍在此頁面上的斷言,以及一組用於公共選擇器的暱稱。

建立頁面

要建立頁面,請使用 Artisan 命令 dusk:page

php artisan dusk:page Dashboard

下面是為我們生成的 Dashboard 檔案:

<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Page as BasePage;

class Dashboard extends BasePage
{
    /**
     * Get the URL for the page.
     *
     * @return string
     */
    public function url()
    {
        return '/';
    }

    /**
     * Assert that the browser is on the page.
     *
     * @return void
     */
    public function assert(Browser $browser)
    {
        $browser->assertPathIs($this->url());
    }

    /**
     * Get the element shortcuts for the page.
     *
     * @return array
     */
    public function elements()
    {
        return [
            '@element' => '#selector',
        ];
    }
}

很顯然,在這上面, url() 方法指明該如何導航到此頁面。而 assert() 方法則寫明瞭:只要這個斷言通過,我仍然在這個頁面上。

elements() 陣列建立了一個簡寫的選擇器,使得你無論什麼何時都可以使用它來引用元素。 綜上所述,我們也可以這樣來填寫這些方法:

class Dashboard extends BasePage
{
    public function url()
    {
        return '/dashboard';
    }

    public function assert(Browser $browser)
    {
        $browser->assertPathIs($this->url());
    }

    public function elements()
    {
        return [
            '@createPost' => '#create-new-post-button',
            '@graphs' => '.dashboard__graphs',
        ];
    }
}

手動為每個網頁上建立自定義的互動方法。 例如,測試中的一個常見行為可能是設定幾個下拉選單,然後單擊 “過濾” 按鈕。 像這樣:

// Dashboard
public function filterGraph($browser, $filterStatus)
{
    $browser->select('filterBy', $filterStatus)
        ->select('limit', 'one-month')
        ->press('Filter');
}

使用頁面

有幾種不同的方式來使用一個頁面。 首先,先訪問它,它既指導瀏覽器,也載入我們的速記選擇器:

use Tests\Browser\Pages\Dashboard;
...

$browser->visit(new Dashboard)
    ->assertSee('@graphs');

如果想通過其他地方的按鈕訪問 new Dashboard 頁面 ,則可以使用 on() 方法來載入:

use Tests\Browser\Pages\Dashboard;
...

$browser->visit('/)
    ->type('email', 'matt@matt.com')
    ->type('password', 'secret')
    ->press('Log in')
    ->on(new Dashboard)
    ->assertSee('@graphs');

也可以使用自定義方法:

$browser->visit(new Dashboard)
    ->filterBy('donors')
    ->assertSee('Sally');

全域性速記選擇器

建立全域性速記選擇器,在預設 test/Browser/Pages/Page 頁面中在您的網站的任何位置使用,這是載入到每個頁面。 將它們新增到 siteElements() 方法中即可。

// tests/Browser/Pages/Page
public static function siteElements()
{
    return [
        '@openChat' => '#chat-box__open-button',
    ];
}

雜記

你已經看到了 Dusk 的強大之處。 下面是幾個隱藏技能:

首先,可以在 .env.dusk.local(或 .env.dusk.任何你想要的測試的環境 )中建立自定義 Dusk 環境檔案。

第二,有一些方法需要 jQuery 來選擇頁面上的內容。Dusk 會檢查你的頁面是否載入 jQuery,如果沒有,會在測試期間注入它。

最後,任何時候測試失敗,Dusk 會為你的報錯頁面截圖存到 tests\Browser\Screenshots 目錄。 這樣一來你就可以看到頁面的確切樣子,相當便利的說~

file

小結

好了,以上就是全部了~雖然,只要拉入舊的測試包,就仍然可以繼續寫之前一直寫的測試。 但是現在有一個全新的世界對你開放。 我建議你不妨嘗試一下。哈~

以上內容翻譯改編自 Matt Stauffer 的 Laravel 5.4 新功能系列文章之 Introducing Laravel Dusk (new in Laravel 5.4)

Stay Hungry, Stay Foolish.

相關文章