Testen - In-Memory-DB vs Mocking

11

Warum sollte jemand beim Schreiben von Tests eine In-Memory-Datenbank verwenden, um nur die Daten zu verspotten?

Ich konnte sehen, dass In-Memory-Datenbanken zum Testen der eigenen Repositorys von Vorteil sein können. Wenn Sie jedoch ein Framework (z. B. Spring Data) verwenden, wird beim Testen der Repositorys das Framework und nicht wirklich die Anwendungslogik getestet.

Das Verspotten scheint jedoch schneller zu sein und folgt dem gleichen Muster, das normalerweise beim Schreiben von Unit-Tests und TDD verwendet wird.

Also, was vermisse ich? Wann / warum wäre eine In-Memory-Datenbank von Vorteil?


quelle

Antworten:

14

Mocking ist die ideale Lösung für Komponententests und kann auch für Integrationstests verwendet werden, um die Geschwindigkeit zu verbessern. Es bietet jedoch nicht das gleiche Maß an Sicherheit wie bei Verwendung einer In-Memory-Datenbank. Sie sollten End-to-End-Tests schreiben, bei denen Sie die gesamte Anwendung so nah wie möglich an der Konfiguration der Produktion konfigurieren und automatisierte Tests für sie ausführen. Diese Tests sollten eine echte Datenbank verwenden - entweder In-Memory, Docker, eine VM oder eine andere Bereitstellung.

Wenn Sie jedoch ein Framework (z. B. Spring Data) verwenden, wird beim Testen der Repositorys das Framework und nicht wirklich die Anwendungslogik getestet.

Indem Sie eine echte Datenbank verwenden, testen Sie, ob Sie das Framework tatsächlich richtig konfigurieren und verwenden. Darüber hinaus kann es Mängel im Framework geben, die nur beim Testen mit einer tatsächlichen Datenbank aufgedeckt werden (erfundenes Beispiel: Spring Data unterstützt Version 9.2 von PostgreSQL nicht).

Ich würde den größten Teil meiner Testberichterstattung gegen verspottete Quellen schreiben, aber ich würde einige End-to-End-Tests für häufig ausgeübte Anwendungsfälle unter Verwendung einer realen Datenbank schreiben.

Samuel
quelle
Wenn es sich um einen Unit-Test handelt, testen Sie das Framework getrennt von der Ebene, die das Framework verwendet. Nach Abschluss aller Unit-Tests sollten immer einige Integrationstests durchgeführt werden.
Denise Skidmore
2

In den meisten Fällen ist das Testen von In-Memory-Datenbanken einfacher als das Verspotten. Es ist auch viel flexibler. Außerdem werden die Migrationsdateien getestet (wenn Migrationsdateien vorhanden sind).

Siehe diesen Pseudocode:

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]');
    }
}

Das InMemoryTesthängt nicht davon ab, wie Databasees in UserRepositorydie Arbeit implementiert wird . Es verwendet einfach die UserRepositoryöffentliche Schnittstelle ( create) und behauptet dann dagegen. Dieser Test wird nicht unterbrochen, wenn Sie die Implementierung ändern, aber er ist langsamer.

In der Zwischenzeit hängt das MockingDBTestvoll davon ab, wie Databasees umgesetzt wird UserRepository. Wenn Sie die Implementierung ändern, sie aber dennoch auf andere Weise funktionieren lassen, wird dieser Test unterbrochen.

Das Beste aus beiden Welten wäre die Verwendung einer Fälschung, die die DatabaseSchnittstelle implementiert :

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;
    }
}

Das ist viel ausdrucksvoller, leichter zu lesen und zu verstehen und hängt nicht von der Implementierung der tatsächlichen Datenbank in höheren Codeebenen ab.

Steve Chamaillard
quelle