iT邦幫忙

2019 iT 邦幫忙鐵人賽

0

今天要來分析內建測試是如何實作的,先來看官方測試範例程式碼:

public function testBasicTest()
{
    $response = $this->get('/');

    $response->assertStatus(200);
}

它對應會跑的 Route 如下:

Route::get('/', function () {
    return view('welcome');
});

Routing 的分析可以再回頭看 Day12

這幾會來看 Laravel 是如何從 get() 去取得 response,並得知 statue code 或其他結果。

共用的 Laravel TestCase

Laravel Framework 有一個抽象的 TestCase 類別,首先要了解測試一開始初始化了哪些東西,這可以從 setUp() 得知:

protected function setUp()
{
    // 初始化 Application
    if (! $this->app) {
        $this->refreshApplication();
    }

    // 初始化 trait
    $this->setUpTraits();

    // Application 初始化後的 hook
    foreach ($this->afterApplicationCreatedCallbacks as $callback) {
        call_user_func($callback);
    }

    // 清除 Facade 的 instance
    Facade::clearResolvedInstances();

    // 重新設定 Model 的 event
    Model::setEventDispatcher($this->app['events']);

    // 當初始化完成,做一個標記
    $this->setUpHasRun = true;
}

Application 是 Laravel 的核心,所以一開始得先初始化。

protected function refreshApplication()
{
    $this->app = $this->createApplication();
}

// createApplication 預設是由 CreateApplication trait 實作的

public function createApplication()
{
    $app = require __DIR__.'/../bootstrap/app.php';

    $app->make(Kernel::class)->bootstrap();

    return $app;
}

分析 bootstrap 流程曾提過,bootstrap/app.php 的任務是提供一個可以用在任何場景的 Application,包括測試。而 bootstrap() 方法是為了載入必要的設定檔等,這樣測試程式碼才能正常使用 Config 等元件。

接著,如果 TestCase 有標記特定的 trait,如 DatabaseTransactions,就會有特定的行為,這個 magic 是由 setUpTraits() 所實作的。後面則是清除設定,讓測試可以從乾淨的狀態從頭執行。

get() 與其他 HTTP 相關的方法是寫在 MakesHttpRequests 裡:

public function get($uri, array $headers = [])
{
    // 將 header 轉成 server 的環境變數
    $server = $this->transformHeadersToServerVars($headers);

    // 呼叫 call
    return $this->call('GET', $uri, [], [], [], $server);
}

其他相關的 HTTP method 與 get() 一樣,最終都會呼叫 call()

public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)
{
    // 取得 HTTP Kernel
    $kernel = $this->app->make(HttpKernel::class);

    // 處理 file 參數
    $files = array_merge($files, $this->extractFilesFromDataArray($parameters));

    // 從各參數產生 symfony request
    $symfonyRequest = SymfonyRequest::create(
        $this->prepareUrlForRequest($uri), $method, $parameters,
        $cookies, $files, array_replace($this->serverVariables, $server), $content
    );

    // 呼叫 handle() 方法
    $response = $kernel->handle(
        $request = Request::createFromBase($symfonyRequest)
    );

    // 如果啟用 followRedirects 就再繼續呼叫 get() 方法,直到不再 redirect
    if ($this->followRedirects) {
        $response = $this->followRedirects($response);
    }

    // Request 結束
    $kernel->terminate($request, $response);

    // 建立 fesponse 測試輔助物件
    return $this->createTestResponse($response);
}

仔細觀察可以發現,它跟 index.php 有很多相同的呼叫方法,而 index.php 有多呼叫 Response send 方法。

最後的 response 測試輔助物件則提供了 assertStatus() 等方法,來驗證最後的 response。


筆者:連續寫了一個半月,真的很累。決定目標設定在 60 天的時候結束。


上一篇
分析 Lumen Application--dispatch() 下篇
下一篇
簡單看看 TestResponse
系列文
Laravel 原始碼分析46

尚未有邦友留言

立即登入留言