web-dev-qa-db-ja.com

テスト-インメモリDBとモッキング

テストを作成するとき、なぜ誰かがデータをモックするだけでなくインメモリデータベースを使用するのでしょうか。

インメモリデータベースは、自分のリポジトリをテストするのに役立つことがわかりました。ただし、フレームワーク(Spring Dataなど)を利用する場合、リポジトリのテストはフレームワークのテストであり、実際にはアプリケーションロジックではありません。

ただし、モッキングはより高速に見え、単体テストやTDDを作成するときに一般的に採用されているのと同じパターンに従っています。

だから私は何が欠けていますか?インメモリデータベースが役立つのはいつ/なぜですか?

12
user253058

モッキングは単体テストの理想的なソリューションであり、速度を向上させる統合テストにも使用できますが、インメモリデータベースを使用する場合と同じレベルの信頼性は提供しません。エンドツーエンドのテストを作成して、アプリケーション全体を可能な限り本番環境で構成する方法に近い形で構成し、それに対して自動テストを実行する必要があります。これらのテストでは、実際のデータベース(インメモリ、Docker、VM、またはその他のデプロイメント)を使用する必要があります。

ただし、フレームワーク(Spring Dataなど)を利用する場合、リポジトリのテストはフレームワークのテストであり、実際にはアプリケーションロジックではありません。

実際のデータベースを使用することで、フレームワークを実際に構成して使用していることをテストできます。さらに、実際のデータベースでテストしたときにのみ明らかになるフレームワークの欠点があるかもしれません(不自然な例:Spring DataはPostgreSQLのバージョン9.2をサポートしていません)。

私はモックソースに対するテストカバレッジのほとんどを記述しますが、実際にデータベースを使用して一般的に実行されるユースケースのエンドツーエンドテストをいくつか記述します。

14
Samuel

ほとんどの場合、インメモリデータベーステストはモックよりも簡単です。また、はるかに柔軟です。また、移行ファイルが適切に実行されていることもテストします(移行ファイルがある場合)。

この擬似コードを参照してください:

class InMemoryTest 
{
    /** @test */
    public function user_repository_can_create_a_user()
    {
        $this->flushDatabase();

        $userRepository = new UserRepository(new Database());
        $userRepository->create('name', '[email protected]');

        $this->seeInDatabase('users', ['name' => 'name', 'email' => '[email protected]']);
    }
}

class MockingDBTest
{
    /** @test */
    public function user_repository_can_create_a_user()
    {
        $databaseMock = MockLib::mock(Database::class);
        $databaseMock->shouldReceive('save')
                     ->once()
                     ->withArgs(['users', ['name' => 'name', 'email' => '[email protected]']]);

        $userRepository = new UserRepository($databaseMock);
        $userRepository->create('name', '[email protected]');
    }
}

InMemoryTestは、DatabaseUserRepositoryにどのように実装されているかには依存しません。単にUserRepositoryパブリックインターフェイス(create)を使用し、それに対してアサートします。実装を変更してもテストは中断しませんが、速度は遅くなります。

一方、MockingDBTestは、DatabaseUserRepositoryに実装される方法に完全に依存しています。実際、実装を変更しても別の方法で機能させると、そのテストは失敗します。

両方の長所はfakeを使用してDatabaseインターフェースを実装することです:

class UsingAFakeDatabaseTest
{
    /** @test */
    public function user_repository_can_create_a_user()
    {
        $fakeDatabase = new FakeDatabase();
        $userRepository = new UserRepository($fakeDatabase);
        $userRepository->create('name', '[email protected]');

        $this->assertEquals('name', $fakeDatabase->datas['users']['name']);
        $this->assertEquals('[email protected]', $fakeDatabase->datas['users']['email']);
    }
}

interface DatabaseInterface
{
    public function save(string $table, array $datas);
}

class FakeDatabase implements DatabaseInterface
{
    public $datas;

    public function save(string $table, array $datas)
    {
        $this->datas[$table][] = $datas;
    }
}

これはより表現力があり、読みやすく、理解しやすく、コードの上位層で行われる実際のデータベースの実装に依存しません。

2