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?

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.


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@email.com');

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

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@email.com']]);

        $userRepository = new UserRepository($databaseMock);
        $userRepository->create('name', 'email@email.com');
    }
}

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@email.com');

        $this->assertEquals('name', $fakeDatabase->datas['users']['name']);
        $this->assertEquals('email@email.com', $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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.