Testowanie - DB w pamięci kontra Mocking

12

Dlaczego pisząc testy, dlaczego ktoś chciałby korzystać z bazy danych w pamięci, a nie tylko wyśmiewać dane?

Widziałem, że bazy danych w pamięci mogą być przydatne do testowania własnych repozytoriów. Ale jeśli używasz frameworka (takiego jak Spring Data), testowanie repozytoriów byłoby testowaniem frameworku, a nie logiki aplikacji.

Wyśmiewanie wydaje się jednak szybsze i opiera się na tym samym schemacie, który zwykle stosuje się podczas pisania testów jednostkowych i TDD.

Więc czego mi brakuje? Kiedy / dlaczego baza danych w pamięci byłaby korzystna?


źródło

Odpowiedzi:

14

Wyśmiewanie jest idealnym rozwiązaniem do testów jednostkowych i może być również wykorzystywane do testów integracyjnych w celu zwiększenia prędkości, ale nie zapewnia takiego samego poziomu pewności, jak w przypadku korzystania z bazy danych w pamięci. Powinieneś pisać kompleksowe testy, w których konfigurujesz całą aplikację tak blisko, jak to możliwe, w jaki sposób jest ona skonfigurowana produkcyjnie i uruchamiasz przeciwko niej automatyczne testy. Te testy powinny korzystać z prawdziwej bazy danych - w pamięci, oknie dokowanym, maszynie wirtualnej lub innym wdrożeniu.

Ale jeśli używasz frameworka (takiego jak Spring Data), testowanie repozytoriów byłoby testowaniem frameworku, a nie logiki aplikacji.

Używając prawdziwej bazy danych testujesz, czy faktycznie konfigurujesz i używasz frameworka poprawnie. Ponadto mogą występować niedociągnięcia w strukturze, które ujawniają się tylko podczas testowania z rzeczywistą bazą danych (wymyślony przykład: Spring Data nie obsługuje wersji 9.2 PostgreSQL).

Większość mojego testu napisałbym na podstawie fałszywych źródeł, ale napisałbym kilka testów end-to-end dla często wykonywanych przypadków użycia przy użyciu prawdziwej bazy danych.

Samuel
źródło
Jeśli jest to test jednostkowy, należy przetestować platformę osobno od warstwy, która korzysta ze struktury. Po zakończeniu wszystkich testów jednostkowych zawsze powinny istnieć testy integracyjne.
Denise Skidmore,
2

W większości przypadków testowanie bazy danych w pamięci jest prostsze niż wyśmiewanie. Jest również o wiele bardziej elastyczny. Testuje także, czy pliki migracji są wykonywane dobrze (gdy istnieją pliki migracji).

Zobacz ten pseudo kod:

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

InMemoryTestNie zależy od tego, jak Databasejest realizowana w UserRepositorycelu pracy. Po prostu używa UserRepositorypublicznego interfejsu ( create), a następnie zapewnia przeciwko niemu. Ten test nie zostanie przerwany, jeśli zmienisz implementację, ale będzie wolniejszy.

Tymczasem w MockingDBTestpełni polega na tym, w jaki sposób Databasejest wdrażanyUserRepository . W rzeczywistości, jeśli zmienisz implementację, ale nadal sprawisz, że będzie działać w inny sposób, test się zepsuje.

Najlepszym z obu światów byłoby użycie fałszywej implementacji Databaseinterfejsu:

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

Jest to o wiele bardziej wyraziste, łatwiejsze do odczytania i zrozumienia i nie zależy od implementacji rzeczywistej bazy danych wykonanej w wyższych warstwach kodu.

Steve Chamaillard
źródło