Laravel テスト時のSQLトランザクション処理はどこで行われているの?


目的

  • テストコード実行時のSQL文をひとつのトランザクションとして扱っている部分を知りたい

確認環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
PHP バージョン 7.4.8 Homebrewを用いてこちらの方法で導入→Mac HomebrewでPHPをインストールする
Laravel バージョン 8.6.0 commposerを用いてこちらの方法で導入→Mac Laravelの環境構築を行う
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

すみません

  • 本記事は筆者のメモ的な記事です。経験の浅い筆者が現状の知識でまとめたものなので参考程度に御覧ください。
  • 間違えている部分はコメントで教えていただけたら幸いです。
  • 時間の都合上じっくり当該処理を確認できそうにないので「おそらくこの辺で処理してそう」くらいまでをまとめます。

おはなしの始まり

  • 最近PhpUnitを用いたテストコードを書き始めた。
  • テーブルからのデータ取得のテストコードは書けるようになったが、データインサートや削除のテストコードは今日はじめて書いた。
  • テスト用のDBを作成してからテストを実施しようとしていたところ先輩から「テスト時のSQLはトランザクションに入っているから開発環境のDBでテストしても問題ないよ」と教えてもらえた。
  • 「じゃあ一体誰がトランザクションに入れてくれているんだい」ということが気になった。
  • その答えを探すべく我々はvendor/laravel/framework/src/Illuminate/ディレクトリの奥地へと向かった。

デフォルトで存在するテストコードファイルを見てみよう

  • デフォルトのテストコードファイルを開いてみる。

    アプリ名ディレクトリ/tests/Feature/ExampleTest.php
    <?php
    
    namespace Tests\Feature;
    
    use Illuminate\Foundation\Testing\RefreshDatabase;
    use Tests\TestCase;
    
    class ExampleTest extends TestCase
    {
        /**
         * A basic test example.
         *
         * @return void
         */
        public function testBasicTest()
        {
            $response = $this->get('/');
    
            $response->assertStatus(200);
        }
    }
    
  • use宣言のRefreshDatabaseが怪しいとみた。

RefreshDatabaseを見に行ってみよう

  • アプリ名ディレクトリ/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.phpを開いてみた。
  • Doc Commentを確認したところbeginDatabaseTransaction()のメソッドが怪しい、下記に当該メソッドを記載する。

    アプリ名ディレクトリ/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php
    /**
     * Begin a database transaction on the testing database.
     *
     * @return void
     */
    public function beginDatabaseTransaction()
    {
        $database = $this->app->make('db');
    
        foreach ($this->connectionsToTransact() as $name) {
            $connection = $database->connection($name);
            $dispatcher = $connection->getEventDispatcher();
    
            $connection->unsetEventDispatcher();
            $connection->beginTransaction();
            $connection->setEventDispatcher($dispatcher);
        }
    
        $this->beforeApplicationDestroyed(function () use ($database) {
            foreach ($this->connectionsToTransact() as $name) {
                $connection = $database->connection($name);
                $dispatcher = $connection->getEventDispatcher();
    
                $connection->unsetEventDispatcher();
                $connection->rollback();
                $connection->setEventDispatcher($dispatcher);
                $connection->disconnect();
            }
        });
    }
    
  • Doc Comment的にRefreshDatabase.phpのbeginDatabaseTransaction()でトランザクションの処理を行っていそうということがわかった。

  • 時間の都合上ここまでとする。本件更に深掘る予定ありなので本記事からの上澄みとして次回は調査したい。

その他の知識

  • $this->connectionsToTransact()は同ファイルで定義されている。
  • property_exists($class, '文字列') ? trueのとき : falseのとき; は$classにプロパティが存在しているがの確認処理である。

    アプリ名ディレクトリ/vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php
    /**
     * The database connections that should have transactions.
     *
     * @return array
     */
    protected function connectionsToTransact()
    {
        return property_exists($this, 'connectionsToTransact')
                            ? $this->connectionsToTransact : [null];
    }