Laravel 測試: PHPUnit 入門教程

Summer__發表於2019-03-01

file

介紹 PHPUnit 測試的基礎知識,使用基本的 PHPUnit 斷言和 Laravel 測試助手。

介紹

PHPUnit 是最古老和最著名的 PHP 單元測試包之一。它主要用於單元測試,這意味著可以用盡可能小的元件測試程式碼,但是它也非常靈活,可以用於很多不僅僅是單元測試。

PHPUnit 包含許多簡單和靈活的斷言允許您輕鬆地測試程式碼,當您測試特定的元件時,這些斷言非常有效。但是,它確實意味著測試更高階的程式碼(如控制器和表單提交驗證)可能會複雜得多。

為了幫助開發人員更容易地進行開發, Laravel 框架  包含了一系列 應用程式測試幫助程式 ,允許您編寫非常簡單的 PHPUnit 測試來測試應用程式的複雜部分。

本教程的目的是向您介紹 PHPUnit 測試的基礎知識,使用預設 PHPUnit 斷言和 Laravel 測試助手。這樣做的目的是在本教程結束時,您可以自信地為應用程式編寫基本測試。

前提

本教程假設您已經熟悉 Laravel 並知道如何在應用程式目錄中執行命令(例如 php artisan 命令)。我們將建立幾個基本的示例類來學習不同的測試工具如何工作,因此建議您為本教程建立一個新的應用程式。

如果已經安裝了 Laravel ,則可以通過執行以下命令建立新的測試應用程式:

laravel new phpunit-tests

複製程式碼

或者,您可以直接使用 Composer 建立新應用程式:

composer create-project laravel/laravel --prefer-dist

複製程式碼

其他安裝方法也可以在 Laravel 文件中找到。

建立一個新的測試

使用 PHPUnit 的第一步是建立一個新的測試類。測試類的約定是它們儲存在應用程式目錄的 ./tests/ 下。在這個資料夾中,每個測試類都被命名為 <name>Test.php 。這種格式允許 PHPUnit 查詢每個測試類---它將忽略任何不以 Test.php 結尾的檔案。

在新的 Laravel 應用程式中,你會注意到 ./tests/ 目錄中有兩個檔案:  ExampleTest.php 和 TestCase.php.  TestCase.php 檔案是一個引導檔案用於在我們的測試中設定 Laravel 環境。這允許我們在測試中使用 Laravel Facades 併為測試助手提供框架,我們將在稍後介紹。 ExampleTest.php 是一個示例測試類,其中包含使用應用程式測試助手的基本測試用例-暫時忽略它。

要建立一個新的測試類,我們可以手動建立一個新檔案,或者執行由 Laravel 提供的 Artisan 命令 make:test

為了建立一個名為 BasicTest 的測試類,我們只需要執行這個 artisan 命令:

php artisan make:test BasicTest

複製程式碼

Laravel 將建立一個如下所示的基本測試類:

<?php
class BasicTest extends TestCase
{
    /**
	* 一個基本的測試示例。
	*
	* @return void
	*/
    public function testExample()
    {
        $this->assertTrue(true);
    }
}

複製程式碼

這裡要注意的最重要的事情是 test 方法名稱上的字首,與 Test 類名字尾一樣,這樣 test 字首告訴 PHPUnit 在測試時執行哪些方法。如果您忘記了 test 字首,那麼 PHPUnit 將忽略該方法。

在我們第一次執行測試套件之前,有必要指出 Laravel 提供的預設 phpunit.xml 檔案。 PHPUnit 在執行時會自動在當前目錄中查詢名為 phpunit.xml 或者 phpunit.xml.dist 的檔案。您可以在此處配置測試的特定選項。

這個檔案中有很多資訊,但是現在最重要的部分是在 testsuite 目錄定義:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit ... >

    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests/</directory>
        </testsuite>
    </testsuites>

    ...
</phpunit>

複製程式碼

這將告訴 PHPUnit 執行時在 ./tests/ 目錄中找到的測試,正如我們之前所知,這是儲存測試的約定。

現在我們已經建立了一個基本測試,並且知道了 PHPUnit 配置,現在是第一次執行測試的時候了。

您可以通過執行以下 phpunit 命令來執行測試:

./vendor/bin/phpunit

複製程式碼

您應該看到與此類似的輸出:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

..

Time: 103 ms, Memory: 12.75Mb

OK (2 tests, 3 assertions)

複製程式碼

現在我們已經有了一個有效的 PHPUnit 設定,現在是時候開始編寫一個基本測試了。

注意,它會統計2個測試和3個斷言,因為 ExampleTest.php 檔案包含了一個帶有兩個斷言的測試。我們的新基本測試包括一個單獨的斷言,該斷言已通過。

寫一個基礎測試

為了幫助 PHPUnit 提供的基本斷言,我們將首先建立一個提供一些簡單功能的基本類

在 ./app/ 目錄中建立一個名為 Box.php 的新檔案,並複製此示例類:

<?php
namespace App;

class Box
{
    /**
	* @var array
	*/
    protected $items = [];

    /**
	* 使用給定項構造框
	*
	* @param array $items
	*/
    public function __construct($items = [])
    {
        $this->items = $items;
    }

    /**
	* 檢查指定的專案是否在框中。
	*
	* @param string $item
	* @return bool
	*/
    public function has($item)
    {
        return in_array($item, $this->items);
    }

    /**
	* 從框中移除項,如果框為空,則為 null 。
	*
	* @return string
	*/
    public function takeOne()
    {
        return array_shift($this->items);
    }

    /**
	* 從包含指定字母開頭的框中檢索所有專案。
	*
	* @param string $letter
	* @return array
	*/
    public function startsWith($letter)
    {
        return array_filter($this->items, function ($item) use ($letter) {
            return stripos($item, $letter) === 0;
        });
    }
}

複製程式碼

接下來, 開啟你的 ./tests/BasicTest.php 類(我們之前建立的類),並刪除預設建立的 testExample 方法, 你應該留一個空類。

我們現在將使用七個基本的 PHPUnit 斷言來為我們的 Box 類編寫測試。這些斷言是:

  • assertTrue()
  • assertFalse()
  • assertEquals()
  • assertNull()
  • assertContains()
  • assertCount()
  • assertEmpty()

assertTrue() 和 assertFalse()

assertTrue() 和 assertFalse() 允許你宣告一個值等於 true 或 false 。這意味著它們非常適合測試返回布林值的方法。在我們的 Box 類中,我們有一個名為 has($item) 的方法,當指定的項在 box 中或不在 box 中時,該方法返回對應返回 true 或 false .

要在 PHPUnit 中為此編寫測試,我們可以執行以下操作:

<?php
use App\Box;

class BasicTest extends TestCase
{
    public function testHasItemInBox()
    {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }
}

複製程式碼

注意我們如何只將一個引數傳遞給 assertTrue() 和 assertFalse() 方法,並且它是 has($item) 方法的輸入.

如果您現在執行 ./vendor/bin/phpunit 命令,您會注意到輸出包括:

OK (2 tests, 4 assertions)

複製程式碼

這意味著我們的測試已經通過。

如果您將 assertFalse() 替換成 assertTrue() 並執行 phpunit 命令,輸出將如下所示:

PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

F.

Time: 93 ms, Memory: 13.00Mb

There was 1 failure:

1) BasicTest::testHasItemInBox
Failed asserting that false is true.

./tests/BasicTest.php:12

FAILURES!
Tests: 2, Assertions: 4, Failures: 1.

複製程式碼

這告訴我們第12行的斷言未能斷言 false 值是 true - 因為我們將 assertFalse() 替換為 assertTrue()

將其交換回來,然後重新執行 PHPUnit 。測試應該再次通過,因為我們已經修復了破損的測試。

assertEquals() 與 assertNull()

接下來,讓我們看看 assertEquals(), 以及 assertNull()

assertEquals() 用於比較變數實際值與預期值是否相等。我們用它來檢查 takeOne() 方法的返回值是否為 Box 內的當前值。當 Box 為空時,takeOne() 將返回 null,我們亦可使用 assertNull() 來進行檢查。

與 assertTrue()assertFalse() 以及 assertNull() 不同,assertEquals() 需要兩個引數。第一個引數為 預期 值,第二個引數則為 實際 值。

可參照如下程式碼實現以上斷言(assertions):

<?php
use App\Box;

class BasicTest extends TestCase
{
    public function testHasItemInBox()
    {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }

    public function testTakeOneFromTheBox()
    {
        $box = new Box(['torch']);

        $this->assertEquals('torch', $box->takeOne());

        // 當前 Box 為空,應當為 Null
        $this->assertNull($box->takeOne());
    }
}

複製程式碼

執行 phpunit 命令,你應當看到如下輸出:

OK (3 tests, 6 assertions)

複製程式碼

assertContains() 和 assertCount() 以及 assertEmpty()

終於,我們有三個作用於陣列有關的斷言,我們能夠使用它們去檢查 Box 類中的  startsWith($item) 方法。 assertContains() 斷言傳遞進來的陣列中包含指定值, assertCount() 斷言陣列的項數為指定數量,assertEmpty() 斷言傳遞進來的陣列為空。

讓我們來執行以下測試:

<?php
use App\Box;

class BasicTest extends TestCase
{
    public function testHasItemInBox()
    {
        $box = new Box(['cat', 'toy', 'torch']);

        $this->assertTrue($box->has('toy'));
        $this->assertFalse($box->has('ball'));
    }

    public function testTakeOneFromTheBox()
    {
        $box = new Box(['torch']);

        $this->assertEquals('torch', $box->takeOne());

        // Null,現在這個 box 是空的。
        $this->assertNull($box->takeOne());
    }

    public function testStartsWithALetter()
    {
        $box = new Box(['toy', 'torch', 'ball', 'cat', 'tissue']);

        $results = $box->startsWith('t');

        $this->assertCount(3, $results);
        $this->assertContains('toy', $results);
        $this->assertContains('torch', $results);
        $this->assertContains('tissue', $results);

        // 如果傳遞複數斷言陣列為空
        $this->assertEmpty($box->startsWith('s'));
    }
}

複製程式碼

儲存並再一次執行你的測試:

OK (4 tests, 9 assertions)

複製程式碼

恭喜你,你剛剛使用七個基礎的 PHPUnit 斷言完成了對 Box 類的全部測試。通過這些簡單的斷言你能夠做許多事,對於其他斷言,大多數要更復雜,不過它們仍遵循以上使用規則。

測試你的程式

在你的程式裡,對每個元件進行單元測試在很多情況下都是有必要的,而且也應該成為你開發過程中必不可少的一部分,但這並不是你需要做的全部的測試。當你構建一個包含複雜檢視、導航和表單的程式時,你同樣想測試這些元件。這時,Laravel的測試助手可以使這些測試像單元測試簡單元件一樣容易。

我們之前檢視在 ./tests/ 目錄下的預設檔案時跳過了 ./tests/ExampleTest.php 檔案。 現在開啟它,內容如下所示:

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

複製程式碼

我們可以看到這個測試示例非常簡單。在不知道測試助手如何運作的情況下,我們可以猜測它的意思如下:

  1. 當我訪問/ (根目錄)
  2. 我應該看到 'Laravel 5'

如果你開啟你的web瀏覽器,訪問我們的程式(如果你沒有啟動你的web伺服器,你可以執行 php artisan serve ),你應該可以在web根目錄上看到螢幕上有“Laravel 5”的文字。 鑑於這個測試已經通過了PHPUnit,我們可以很確定地說我們對這個測試示例改造是正確的。

這個測試確保了訪問/路徑,網頁可以返回“'Laravel 5”的文字。一個如此簡單的檢查也許不代表什麼,但如果你的網站上要顯示關鍵資訊,它就可以在一個別處的改動導致這個頁面無法正常顯示正確的資訊時,防止你部署一個被損壞的程式。

visit()、see() 以及 dontSee()

現在嘗試編寫自己的測試,更進一步理解它吧。

首先,編輯 ./app/Http/routes.php ,增加一個新的路由。為了教程目的,我們建立希臘字母定義的路由:

<?php
Route::get('/'function () {
    return view('welcome');
});

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

複製程式碼

然後,建立檢視檔案 ./resources/views/alpha.blade.php,使用 Alpha 作為關鍵字,儲存基本的HTML檔案:

<!DOCTYPE html>
<html>
    <head>
        <title>Alpha</title>
    </head>
    <body>
        <p>This is the Alpha page.</p>
    </body>
</html>

複製程式碼

開啟瀏覽器,輸入網址: http://localhost:8000/beta,頁面會顯示出 "This is the Alpha page." 的內容。

現在我們有了測試用到的模版檔案,下一步,我們通過執行命令 make:test 來建立一個新的測試檔案:

php artisan make:test AlphaTest

複製程式碼

然後變成剛建立好的測試檔案,按照框架提供的例子,測試 "alpha" 頁面上沒有包含 "beta" 。 我們可以使用方法 dontSee() ,它是 see() 的對應的反向方法。

下面程式碼是上面實現的簡單例子:

<?php
class AlphaTest extends TestCase
{
    public function testDisplaysAlpha()
    {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta');
    }
}

複製程式碼

儲存並執行 PHPUnit (./vendor/bin/phpunit),測試程式碼應該會全部通過,你會看到像這樣的測試狀態內容顯示:

OK (5 tests,12 assertions)

複製程式碼

開發前先寫測試

對於測試來說,測試驅動開發 (TDD) 是非常酷的方法,首先我們先寫測試。寫完測試並執行它們,你會發現測試沒通過,接下來 我們編寫滿足測試的程式碼,再次執行測試,使測試通過。 接下來讓我們開始。

首先,建立一個 BetaTest 類使用 make:test artisan 命令:

php artisan make:test BetaTest

複製程式碼

接下來,更新測試用例以便檢查 /beta 的路由 route 為「Beta」:

<?php
class BetaTest extends TestCase
{
    public function testDisplaysBeta()
    {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha');
    }
}

複製程式碼

現在使用 ./vendor/bin/phpunit 命令來執行測試。結果是一個看起來簡潔但不好的錯誤資訊,如下:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

....F.

Time: 144 ms, Memory: 14.25Mb

There was 1 failure:

1) BetaTest::testDisplaysBeta
一個對 [http://localhost/beta] 的請求失敗了。收到狀態碼 [404]。

...

FAILURES!
Tests: 6, Assertions: 13, Failures: 1.

複製程式碼

我們現在需要建立這個不存在的路由。讓我們開始。

首先,編輯 ./app/Http/routes.php 檔案來建立新的 /beta 路由:

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

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

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

複製程式碼

接下來,在 ./resources/views/beta.blade.php 下建立如下檢視模版:

<!DOCTYPE html>
<html>
    <head>
        <title>Beta</title>
    </head>
    <body>
        <p>This is the Beta page.</p>
    </body>
</html>

複製程式碼

現在再一次執行 PHPUnit,結果應該再一次回到綠色。

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

......

Time: 142 ms, Memory: 14.00Mb

OK (6 tests, 15 assertions)

複製程式碼

這樣我們就通過在完成新的頁面之前寫測試的方式,對 測試驅動開發 進行了實踐。

click() 和 seePageIs()

Laravel 也提供一個輔助函式 (click()) 允許測試點選頁面中存在的連線 ,以及一個方法 (seePageIs()) 檢查點選展示的結果頁面。

讓我們使用這兩個輔助函式去執行在 Alpha 和 Beta 頁面的連結。

首先,我們更新我們的測試。開啟 AlphaTest 類,我們將新增一個新的測試方法,這將點選 「alpha」頁面上的「Next」連結跳轉到 「beta」頁面。

新的測試程式碼如下:

<?php
class AlphaTest extends TestCase
{
    public function testDisplaysAlpha()
    {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta');
    }

    public function testClickNextForBeta()
    {
        $this->visit('/alpha')
             ->click('Next')
             ->seePageIs('/beta');
    }
}

複製程式碼

注意到,在我們新建的 testClickNextForBeta() 方法中,我們並沒有檢查每一個頁面的內容。 其他測試都成功的檢查了兩個頁面的內容,所以這裡我們只關心點選 「Next」連結將傳送到 /beta

你現在可以執行測試元件了,但就像預料的一樣測試將不通過,因為我們還沒有更新我們的 HTML。

接下來,我們將更新 BetaTest 來做類似的事情:

<?php
class BetaTest extends TestCase
{
    public function testDisplaysBeta()
    {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha');
    }

    public function testClickNextForAlpha()
    {
        $this->visit('/beta')
             ->click('Previous')
             ->seePageIs('/alpha');
    }
}

複製程式碼

接下來,我們更新我們的 HTML 模版。

./resources/views/alpha.blade.php

<!DOCTYPE html>
<html>
    <head>
        <title>Alpha</title>
    </head>
    <body>
        <p>This is the Alpha page.</p>
        <p><a href="/beta">Next</a></p>
    </body>
</html>

複製程式碼

./resources/views/beta.blade.php

<!DOCTYPE html>
<html>
    <head>
        <title>Beta</title>
    </head>
    <body>
        <p>This is the Beta page.</p>
        <p><a href="/alpha">Previous</a></p>
    </body>
</html>

複製程式碼

儲存檔案,再一次執行 PHPUnit:

> ./vendor/bin/phpunit
PHPUnit 4.8.19 by Sebastian Bergmann and contributors.

F....F..

Time: 175 ms, Memory: 14.00Mb

There were 2 failures:

1) AlphaTest::testDisplaysAlpha
Failed asserting that '<!DOCTYPE html>
<html>
    <head>
        <title>Alpha</title>
    </head>
    <body>
        <p>This is the Alpha page.</p>
        <p><a href="/beta">Next</a></p>
    </body>
</html>
' does not match PCRE pattern "/Beta/i".

2) BetaTest::testDisplaysBeta
Failed asserting that '<!DOCTYPE html>
<html>
    <head>
        <title>Beta</title>
    </head>
    <body>
        <p>This is the Beta page.</p>
        <p><a href="/alpha">Previous</a></p>
    </body>
</html>
' does not match PCRE pattern "/Alpha/i".

FAILURES!
Tests: 8, Assertions: 23, Failures: 2.

複製程式碼

然而測試失敗了。如果你仔細觀察我們的新 HTML,你將注意到我們分別有術語 beta 和 alpha 在 /alpha 和 /beta 頁面。這意味著我們需要稍微更改我們的測試讓它們與誤報不匹配。

在每一個 AlphaTest 和 BetaTest 類,更新 testDisplays* 方法去使用 dontSee('<page> page')。通過這種方式,這將僅僅匹配字串而不是那個術語。

兩個測試檔案如下所示:

./tests/AlphaTest.php

<?php
class AlphaTest extends TestCase
{
    public function testDisplaysAlpha()
    {
        $this->visit('/alpha')
             ->see('Alpha')
             ->dontSee('Beta page');
    }

    public function testClickNextForBeta()
    {
        $this->visit('/alpha')
             ->click('Next')
             ->seePageIs('/beta');
    }
}

複製程式碼

./tests/BetaTest.php

<?php
class BetaTest extends TestCase
{
    public function testDisplaysBeta()
    {
        $this->visit('/beta')
             ->see('Beta')
             ->dontSee('Alpha page');
    }

    public function testClickNextForAlpha()
    {
        $this->visit('/beta')
             ->click('Previous')
             ->seePageIs('/alpha');
    }
}

複製程式碼

再一次執行你的測試,所有的測試都應該通過了。我們現在已經測試我們所有的新檔案,包括頁面中的 Next/Previous 連結。

通過 Semaphore 對 PHPUnit 持續整合

通過 Semaphore設定 持續整合你可以自動執行你的測試。

這樣每一次你進行 git push 提交程式碼的時候都會執行你的測試,並且 Semaphore 預裝了所有最新的 PHP 版本

如果你還沒有一個 Semaphore 賬戶, 先去 註冊一個免費的 Semaphore 賬戶 。接下來需要做的是將它 新增到你的專案,並按照提示逐步去做來執行你的測試:

composer install --prefer-source
phpunit

複製程式碼

關於 PHP 持續整合 的更多資訊,請參照 Semaphore 文件。

結語

你應該注意到本教程中的所有測試都有一個共同的主題:它們都非常簡單。 這是學習如何使用基本的測試斷言和輔助函式,並且儘可能的使用它們的好處之一。編寫測試越簡單,測試就越容易理解和維護。

掌握了本教程中介紹的 PHPUnit 斷言之後,你還可以去 PHPUnit 文件 找到更多內容。 所有的斷言都遵循基本的模式,但你會發現,在大多數測試中都會返回基本的斷言。

對於 PHPUnit 斷言來說,Laravel 的測試輔助函式是極好的補充,這讓應用程式的測試變的非常容易。也就是說,重要的是要認識到,對於我們寫測試,我們只檢查關鍵資訊,而不是整個頁面。這使得測試變得簡單,並允許頁面內容隨著應用程式的變化而變化。如果關鍵資訊仍然存在,測試仍然通過,每個人都會滿意。

文章轉自: https://learnku.com/laravel/t/24559
更多文章:https://learnku.com/laravel/c/translations

相關文章