LoginSignup
34
22

More than 1 year has passed since last update.

【Laravel】PHPUnitの実行でDBを汚染しないためにできる簡単なこと

Last updated at Posted at 2022-02-25

環境

OS: Windows 10
PHP: 8.1.2
Laravel Framework: 8.81.0
docker-compose: 1.29.2
PHPUnit: 9.5.10
MySQL: 8.0

Laravel Sailを使用して、Laravelプロジェクトを立ち上げています。
プロジェクトを立ち上げる部分は、こちらのドキュメントでご確認ください。

RefreshDatabase DBの状態に影響されずテストする

use RefreshDatabase;をしてあげると、
テストを実行する際に前後のDBの状態を考える必要がなくなります

各テスト毎にDBをリフレッシュするため、まっさらな状態にしてくれるので、
DBの状態を確認するアサーションである

  • $this->assertDatabaseCount(...);
  • $this->assertDatabaseHas(...);
  • $this->assertDatabaseMissing(...);

あたりが扱いやすくなります。

そして、テストによって追加されてしまうデータも残らないので安心です。
では、実際に動作を見るためにテストを作成しましょう。

テストの作成

まずは、テストの作成のために以下のコマンドを叩きます。

UserTestの追加
$ ./vendor/bin/sail artisan make:test Model/UserTest

そして、作成されたファイルを以下のように修正します。

tests/Feature/Model/UserTest.php
<?php

namespace Tests\Feature\Model;

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

class UserTest extends TestCase
{
    use RefreshDatabase;

    const TABLE_NAME = 'users';

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

    /**
     * @return int
     * @throws \Exception
     */
    private function factory(): int
    {
        // 1~9のランダムな数値を取得
        $count = random_int(min: 1, max: 9);

        // $countの数だけ、新規にUserを作成する
        User::factory($count)->create();

        return $count;
    }

    /**
     * @test
     * @group Model
     * @group User
     *
     * @return void
     * @throws \Exception
     */
    public function 全件取得(): void
    {
        $count = $this->factory();

        // usersテーブルに$countの数だけデータが挿入されているか
        $this->assertDatabaseCount(table: self::TABLE_NAME, count: $count);

        $result = User::all();

        // 全件取得できているか
        $this->assertCount(expectedCount: $count, haystack: $result);
    }
}

さて、テストが作成できましたので、
これを動作させてみましょう。

テストの実行

テストの実行のためには、以下のコマンドを叩きます。

テストの実行
$ ./vendor/bin/sail test --group=Model

今回のテストには$this->assertDatabaseCount(...);が含まれています。
もし、DBの状態がリフレッシュされているならば、連続で実行しても問題ないはずです。

試しに3回ほど実行してみましょう!

実行結果
$ ./vendor/bin/sail test --group=Model

   PASS  Tests\Feature\Model\UserTest
  ✓ 全件取得

  Tests:  1 passed
  Time:   0.33s

$ ./vendor/bin/sail test --group=Model

   PASS  Tests\Feature\Model\UserTest
  ✓ 全件取得

  Tests:  1 passed
  Time:   0.30s

$ ./vendor/bin/sail test --group=Model

   PASS  Tests\Feature\Model\UserTest
  ✓ 全件取得

  Tests:  1 passed
  Time:   0.29s

問題なくPASSしていますね!

これでDBの状態に影響されずテストができることが分かります。

テスト後のDBの状態を確認

次にテストによって追加されてしまうデータが残っていないことを確認します。

確認方法は何でも良いのですが、今回はできる限り簡潔に画面に表示します。
以下の実装を行いましょう。

routes/web.php
<?php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    $users = \App\Models\User::all();

    dd($users);
});

あとは、http://localhost/に接続すれば・・・

image.png

出ました!!

#items: []ということは、何のデータも入っていないことなので、
これでテストによって追加されてしまうデータが残っていないことが分かりました。

本当に影響がないか確認

テストがデータに影響しないこともデータが残ってないことも分かりました。

では、本当に何も影響がないのでしょうか?

それを確認するためにダミーデータを入れてからテストを実行してみましょう。

ダミーデータの作成

まずは、ダミーデータを作成するためにシーダーを作成する必要がありますので、
シーダーを作成するために以下のコマンドを叩きます。

シーダーの作成
$ ./vendor/bin/sail artisan make:seeder UserSeeder

シーダーの作成ができたら、ダミーデータを作成するために実装しましょう。
以下の修正を加えればOKです。

database/seeders/UserSeeder.php
<?php

namespace Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;

class UserSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        User::factory(10)->create();
    }
}
database/seeders/DatabaseSeeder.php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call([
            UserSeeder::class,
        ]);
    }
}

これでダミーデータを作成する準備が整ったので、以下のコマンドを叩きましょう。

ダミーデータの作成
$ ./vendor/bin/sail artisan db:seed

作成しているかどうか、http://localhost/に接続して確認してみます。

image.png

#items: array:10 [▶]となっているので作成できていますね。

結果は・・・

では、テストを実行してから確認してみましょう。

テストの実行
$ ./vendor/bin/sail test --group=Model

そして、http://localhost/に接続・・・

image.png

き、消えてる・・・。

ということで、汚染しない代わりに綺麗に全てを消してしまうのです。

解決方法

テスト実行の度にダミーデータを挿入するのも手間がかかりますし、
手動で追加したデータなどは消えてしまいます。
これでは、根本的な解決になりません。

根本的な解決方法を考えるなら
テストのときは違うDB環境を使用すればよいのです。

sqliteを使用する

設定は何もこだわらなければ、とても簡単です。

以下のように、コメントアウトを外すだけです。

phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
>
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>
        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">./app</directory>
        </include>
    </coverage>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="sqlite"/>
        <server name="DB_DATABASE" value=":memory:"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <server name="TELESCOPE_ENABLED" value="false"/>
    </php>
</phpunit>

これで設定完了です。

テスト実行(sqlite)

先程と同じようにダミーデータの作成し、テストを実行してみます。

ダミーデータの作成
$ ./vendor/bin/sail artisan db:seed
テストの実行
$ ./vendor/bin/sail test --group=Model

そして、http://localhost/に接続・・・

image.png

やりました!
ダミーデータが残ったままです!

テストで違うDB環境を使用する

簡単な設定でしたが、残念ながら
外部キー制約などがあると、sqliteを使うことはできません・・・。

そんなときは、大人しく別のDBを用意する方法が良いでしょう。

.env.testing

まず、.env.testingを作成します。
一番楽なのは、.envを複製してリネームすることです。

重要なのは、
APP_KEYにキーがあることDB_HOST.envと異なることです。

今回は、DB_HOSTmysql.testとします。

.env.testing
...

APP_KEY=(キーが入っている)

...

DB_CONNECTION=mysql
DB_HOST=mysql.test
DB_PORT=(.envと一緒)
DB_DATABASE=(.envと一緒)
DB_USERNAME=(.envと一緒)
DB_PASSWORD=(.envと一緒)

...
APP_KEYにキーが入っていない

その場合は、以下のコマンドで生成しましょう。

キーの生成
$ ./vendor/bin/sail artisan key:generate --env=testing

docker-compose.yml

先程、.env.testingで追加したDB_HOSTを追加します。
今回はmysql.testですね。

docker-compose.yml
    mysql.test:
        image: 'mysql/mysql-server:8.0'
        environment:
            MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ROOT_HOST: "%"
            MYSQL_DATABASE: '${DB_DATABASE}'
            MYSQL_USER: '${DB_USERNAME}'
            MYSQL_PASSWORD: '${DB_PASSWORD}'
            MYSQL_ALLOW_EMPTY_PASSWORD: 1
        networks:
            - sail

テスト用に使用するだけなので、
ports、volumes、healthcheckなどは不要です。

追加した後は、sailを再度立ち上げましょう!

sailの立ち上げ
$ ./vendor/bin/sail up -d

テスト実行(別DB)

テストを実行してみます。

ダミーデータの作成
$ ./vendor/bin/sail artisan db:seed
テストの実行
$ ./vendor/bin/sail test --group=Model

そして、http://localhost/に接続・・・

image.png

こちらでも問題なく残っていますね!

まとめ

ということで、上記の手順によりDBの汚染を防ぐことができます。

テストを記述するのはとても大切だと思っているので、
備忘録ではありますが、この記事があなたの役に立てば嬉しいです。

34
22
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
22