LoginSignup
2
2

More than 1 year has passed since last update.

Laravelの自動テストでDBの汚染を避けようとした結果、DBのテーブルを全て吹き飛ばした話

Posted at

Laravelの自動テストでDBの汚染を避けようとした結果、DBのテーブルを全て吹き飛ばした話

タイトルの通りですが、自分のようにドキュメントを流し読みしてしまっただけで同じ過ちを犯してしまう人が出ないよう、また使用するライブラリのコードはきちんと読みましょうという自身への戒めを兼ねて記録します。

自動テスト実行時に生成されたレコードを共有開発環境のDBに残したくない

この問いに対してLaravelは有効な機能を提供してくれています。
今回の主役である Illuminate\Foundation\Testing\RefreshDatabaseトレイト です。

前のテストがその後のテストデータに影響しないように、各テストの後にデータベースをリセットできると便利です。インメモリデータベースを使っていても、トラディショナルなデータベースを使用していても、RefreshDatabaseトレイトにより、マイグレーションに最適なアプローチが取れます。テストクラスにてこのトレイトを使えば、すべてが処理されます。
https://readouble.com/laravel/7.x/ja/database-testing.html

早速traitをuseして使ってみる

  • 結論、下記のテストによって作成されたusersテーブルのレコードは残りません。が、それ以前の大問題が発生していました。
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class ExampleTest extends TestCase
{
    use RefreshDatabase; // トレイトを使用する

    public function test_record_created_by_this_test_does_not_persist()
    {
        User::factory()->create();
    }
}

数十あったテーブルがすべて消えて4つほどの見慣れないテーブルのみが残っている

私が上記の操作をして、数分もたたないうちにチーム内から、開発環境が全く動かないという声が多数上がりました。
そしてどの画面もBase table [] not foundのエラー。

結論から言うと、チームで共有している開発用DBに接続している状態で、RefreshDatabaseトレイトを使用してしまった結果、その開発用DBにあるテーブルが消え去ってしまったのでした。

何が起きたのか

答えは当然ながらRefreshDatabaseトレイトのコードにありました。
下記はRefreshDatabaseを使用した状態でのテストで実行されるコードです。

関数内の2行目でphp artisan migrate:freshに相当する処理の実行が見て取れます。
その後、最下部でトランザクションが張られ、その中でテストが行われる流れのようです。
トランザクションで保護されるのは、migration実行後の状態であり、もともとのデータベースの状態ではなかったことを理解していなかったというのが今回の直接的なミスの原因でした。

私たちのチームでは普段migrationを使用していませんでした。
そのためmigrationファイルはLaravelインストール時にデフォルトで含まれている4テーブル分のみ。
結果として、テスト終了後にトランザクションが終了して残るのはmigrate:freshコマンドによって作成された4テーブルとなります。

    /**
     * Refresh a conventional test database.
     *
     * @return void
     */
    protected function refreshTestDatabase()
    {
        if (! RefreshDatabaseState::$migrated) {
            $this->artisan('migrate:fresh', $this->migrateFreshUsing());

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
    }

結局どうなったか

今回は社内での使用のみのテスト環境であり、AWS RDSで自動バックアップも取得していたためそこから復元し、大事には至りませんでしたが、接続先が本番環境であったり、バックアップを取得していなかったこと等考えると本当に肝が冷えました。

ただ、ここでinsert / delete系のテストは行わない、となってしまうとテストを導入していく意味合いが薄れてしまうため、マイグレーション環境を整えて、sqliteを使用したインメモリデータベースでのテストを導入する等の方向が取れないかさらに調査していきたいと思います。

2
2
0

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
2
2