Pest 對我來說提供了更便捷的測試方式和直譯式的寫法,類似 JS 相關的測試框架,同時又保留了 Laravel 和 PHP 龐大的輔助函數和功能。

主結構

$ composer require pestphp/pest --dev --with-all-dependencies
$ ./vendor/bin/pest --init
$ ./vendor/bin/pest

folders:

├── 📂 tests

│ ├── 📂 Unit

│ │ └── ExampleTest.php

│ └── 📂 Feature

│ │ └── ExampleTest.php

│ └── TestCase.php

│ └── Pest.php

├── phpunit.xml

簡單示例

這邊可以自己添加 phpunit.xml 對應路徑,可以看到 Pest.php 實現細節:

這邊表示注入 TestCase 到 Feature 底下,可以使用 TestCase 裡面所提供的方法,當然也可以注入對應的方法到指定的 folder 底下:

uses(
    Tests\TestCase::class,
    // Illuminate\Foundation\Testing\RefreshDatabase::class,
)->in('Feature');

SumTest

<?php

function sum(int $int, int $int1)
{
    return $int + $int1;
}

test('sum', function () {
    $result = sum(1, 2);

    expect($result)->toBe(3);

});

it('performs sums', function () {
    $result = sum(1, 2);

    expect($result)->toBe(3);
});

Pest 提供相當多的 expectations 當然你也可以自行擴展:

expect()->extend('toBeSureStatusEnabled', function (int $expected) {
    return $this->toEqual($expected);
});

Hooks

這邊提供事件鉤子來讓你作 injection 和相關初始化參數動作。 以常用情境來說,我會需要測試 UserRepository 相關對應方法,我希望他能在 beforeEach 之前建立:

UserRepository.php

class UserRepository
{
    public function __construct(protected User $user) {}

    public function create(array $attributes = []): User
    {
        return $this->user->create($attributes);
    }
}

UserRepositoryTest.php

beforeEach(function () {
   $this->userRepository = app(\App\Repositories\UserRepository::class);
});

it('created a user.', function () {
        $user = $this->userRepository->create([
            'name' => $name = fake()->name(),
            'email' => $email = fake()->unique()->safeEmail(),
            'hash' => gzcompress(Str::random()),
            'email_verified_at' => now(),
            'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
            'remember_token' => Str::random(10),
        ]);

        expect($user)->toBeInstanceOf(\App\Models\User::class);
        $this->assertDatabaseHas('users', [
            'name' => $name,
            'email' => $email,
        ]);
});

Datasets

這個功能其實就像是 dataprovider 這樣理解就會比較容易了。

it('has emails', function (string $email) {

expect($email)->not->toBeEmpty();

})->with(['[email protected]', '[email protected]']);

Exceptions

就是原本的 expectsExceptions:

it('throws exception', function () {

throw new Exception('Something happened.');

})->throws(Exception::class);

還有幾個後續支持的 --parallel, --processes 等優化測試參數都有支持,此外我自己是比較喜歡 --profile 這個功能可以讓我知道我哪些測試在執行效能上有問題:

  Tests:    6 passed (7 assertions)
  Duration: 14.66s

  Top 10 slowest tests:
  Tests\Feature\ExampleTest > the application returns a successful response                 4.42s
  Tests\Feature\SumTest > it has emails with ('[email protected]')                      3.22s
  Tests\Feature\SumTest > it created a user.                                                1.22s
  Tests\Feature\SumTest > it has emails with ('[email protected]')                          0.42s
  Tests\Feature\SumTest > it performs sums                                                  0.32s
  Tests\Unit\ExampleTest > that true is true                                                0.19s
  ───────────────────────────────────────────────────────────────────────────────────────────────
                                                                         (66.75% of 14.66s) 9.79s

總結

整體來說我覺得作為一款輔助 unit testing 的協同框架是非常不錯的,它提供一種流暢和易懂的寫法來撰寫單元測試,但必須注意的是它底層一樣是依賴 phpunit 框架,所以還是需要知道 phpunit 是如何使用跟運作,而把 pest 當成是一種協同、換個方式描述的測試框架使用會比較好,我認為他情境適合用於穩定且探索新做法的專案當中,初期專案我還是會比較傾向於使用內建的作法會比較方便協作。