Laravel10から公式にテストフレームワークとしておすすめされるようになったPestですが、すでにPHPUnitで作成したテストファイルがたくさんある場合、それらを書き換えての移行は大変です。そこで、Pestが提供している変換用プラグインpestphp/pest-plugin-driftを試してみました。

Pest変換対象のLaravelプロジェクト

今回Pestへ変換するLaravelプロジェクトは以下の環境です。テスト用の小さいプロジェクトなので、テストファイルは9ファイルのみとなっています。

Laravel Framework 11.29.0
PHPUnit 11.4.2
PHP 8.2.24

Pest・Pest変換用プラグインインストール

では早速Pestからインストールします。Pest公式サイトの手順に従って進めます。

まずはPHPunitを削除。

$ composer remove phpunit/phpunit

次に、以下のコマンドでPestをインストール。

$ composer require pestphp/pest --dev --with-all-dependencies

Pestのバージョン3.5がインストールされました。続いてinitを実行します。

$ ./vendor/bin/pest --init

すると以下のファイルが作成されました。すでに存在しているファイルはパスされるので、実質Pest.phpのみが作成された形です。

これでPest本体のインストールは完了です。テストファイルの記述はまだPHPUnitのままですが、この段階でも./vendor/bin/pestが実行できます。

続いて変換用プラグインをインストールしてゆきます。こちらも公式サイトの手順に従って、以下のコマンドを実行します。

$ composer require pestphp/pest-plugin-drift --dev

バージョン3.0.0がインストールされました。最後に、以下のコマンドでPHPUnitからPestへの変換を実行します。

$ ./vendor/bin/pest --drift

既存のテストファイルが全て変換されました。

今回は小さいシンプルなプロジェクトなので各種インストールはとくに問題なく進みましたが、大きなプロジェクトの場合依存関係の問題でインストールがスムーズにいかないこともあると思います。

PHPUnit → Pest変換前後の比較

では、変換されたファイルを前後比較してみます。まず、以下が変換前のPHPUnit仕様のテストです。setUpdataProviderも使用したよくあるテストコードです。

namespace Tests\Feature\Auth;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AuthenticationTest extends TestCase
{
    use RefreshDatabase;

    protected $user;

    protected function setUp(): void
    {
        parent::setUp();

        // 共通ユーザー作成
        $this->user = User::factory()->create([
            'email'    => 'test@example.com',
            'password' => 'password'
        ]); 
    }

    /**
     * @covers \App\Http\Controllers\Auth\LoginController::showLoginForm
     */
    public function test_login_screen_can_be_rendered(): void
    {
        $response = $this->get('/login');
        $response->assertStatus(200);
    }

    /**
     * @covers \App\Http\Controllers\Auth\LoginController::login
     */
    public function test_users_can_authenticate_with_valid_credentials(): void
    {
        $response = $this->post('/login', [
            'email'    => 'test@example.com',
            'password' => 'password',
        ]);

        $this->assertAuthenticated();
        $this->assertEquals('test@example.com', auth()->user()->email);
        
        $response->assertRedirect(route('dashboard', absolute: false));
    }

    /**
     * @covers \App\Http\Controllers\Auth\LoginController::login
     * @dataProvider invalidLoginDataProvider
     */
    public function test_users_cannot_authenticate_with_invalid_credentials($email, $password): void
    {
        $response = $this->post('/login', [
            'email'    => $email,
            'password' => $password,
        ]);

        $this->assertGuest();
        $response->assertStatus(302);
    }

    public static function invalidLoginDataProvider(): array
    {
        return [
            '間違ったパスワード'    => ['test@example.com', 'wrong-password'],
            '間違ったメールアドレス' => ['wrong@example.com', 'password'],
        ];
    }

    /**
     * @covers \App\Http\Controllers\Auth\LoginController::logout
     */
    public function test_users_can_logout(): void
    {
        $response = $this->actingAs($this->user)->post('/logout');

        $this->assertGuest();
        $response->assertRedirect('/');
    }
}

そして、Pestに変換された後のテストがこちらです。

use App\Models\User;

uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

beforeEach(function () {
    // 共通ユーザー作成
    $this->user = User::factory()->create([
        'email'    => 'test@example.com',
        'password' => 'password'
    ]);
});

test('login screen can be rendered', function () {
    $response = $this->get('/login');
    $response->assertStatus(200);
});

test('users can authenticate with valid credentials', function () {
    $response = $this->post('/login', [
        'email'    => 'test@example.com',
        'password' => 'password',
    ]);

    $this->assertAuthenticated();
    expect(auth()->user()->email)->toEqual('test@example.com');

    $response->assertRedirect(route('dashboard', absolute: false));
});

test('users cannot authenticate with invalid credentials', function ($email, $password) {
    $response = $this->post('/login', [
        'email'    => $email,
        'password' => $password,
    ]);

    $this->assertGuest();
    $response->assertStatus(302);
})->with('invalidLoginDataProvider');

dataset('invalidLoginDataProvider', function () {
    return [
        '間違ったパスワード'    => ['test@example.com', 'wrong-password'],
        '間違ったメールアドレス' => ['wrong@example.com', 'password'],
    ];
});

test('users can logout', function () {
    $response = $this->actingAs($this->user)->post('/logout');

    $this->assertGuest();
    $response->assertRedirect('/');
});

変換後のPest仕様のコードではクラスの定義は削除され、それぞれのテストケースは関数宣言ではなくtest()関数として記述されています。また変換前は85行あったテストコードがPest変換後は57行とかなりコンパクトになりました。Docコメントが全て削除されているのが大きいのかもしれません。

それでは、移行するにあたり気になっていた箇所をピックアップしてみてみます。

dataprovider

PHPUnitのdataProviderは、Pest変換後は以下のようになりました。Docコメントとともに@dataProviderの記述は削除されて、代わりにwith()でデータセットを紐付けしています。テストとデータセットがバラバラなので、見た目にはPHPUnitとあまり変わりがありませんね。

test('users cannot authenticate with invalid credentials', function ($email, $password) {
・・・・・
})->with('invalidLoginDataProvider');

dataset('invalidLoginDataProvider', function () {
    return [
        '間違ったパスワード'    => ['test@example.com', 'wrong-password'],
        '間違ったメールアドレス' => ['wrong@example.com', 'password'],
    ];
});

データセットの紐付けは、以下のようにwith()内に記述される形に変換されることを期待したのですが、そうはなりませんでした。

test('users cannot authenticate with invalid credentials', function ($email, $password) {
    ・・・・・
    })->with([
        ['test@example.com', 'wrong-password'],
        ['wrong@example.com', 'password']
]);

setUp関連

PHPUnitのsetUp()は、以下のようにbeforeEach()に変換されました。beforeEach()とは、それぞれのテストメソッドの実行前に毎回呼び出されるsetUp()と同じように共通の処理を行う際に使用されるメソッドです。

beforeEach(function () {
    // 共通ユーザー作成
    $this->user = User::factory()->create([
        'email'    => 'test@example.com',
        'password' => 'password'
    ]);
});

他にも、PHPUnitで当然のように書いていたparent::setUp()use RefreshDatabase;はPestでは書く必要がなく、またprotected $user;といったプロパティ宣言も不要です。Pestの謳い文句の通り、テストコードはだいぶシンプルにできますね。

テスト実行

では変換後のテストを実行してみましょう。

テストはコマンドラインで、

$ ./vendor/bin/pest

あるいは、

$ php artisan test

で実行できます。

以下のようにオプションを渡して実行ファイルを指定することもできます。

$ ./vendor/bin/pest --filter=AuthenticationTest //ファイル名を指定する場合

実行結果は以下のように出力されました。

テストにかかる時間はPestに変換しても速くなるということは特になく、PHPUnitと変わりなかったです。

今回は自動で変換したためPestの記述に関しては要点のみとなりましたが、次回は手動でPestのテストコードを作成してゆきます。

メルマガ購読の申し込みはこちらから。

By hmatsu