Test - DB in memoria vs derisione


12

Quando si scrivono i test, perché qualcuno dovrebbe voler usare un database in memoria solo per deridere i dati?

Ho visto che i database in memoria potrebbero essere utili per testare i propri repository. Ma se si utilizza un framework (come Spring Data), testare i repository testerebbe il framework e non proprio la logica dell'applicazione.

Il derisione, tuttavia, sembra più rapido e segue lo stesso schema generalmente utilizzato quando si scrivono unit test e TDD.

Quindi cosa mi sto perdendo? Quando / perché un database in memoria sarebbe utile?

Risposte:


14

Il derisione è la soluzione ideale per i test unitari e può essere utilizzato anche per i test di integrazione per migliorare la velocità, ma non fornisce lo stesso livello di sicurezza di quando si utilizza un database in memoria. È necessario scrivere test end-to-end in cui si configura l'intera applicazione il più vicino possibile al modo in cui è configurata la produzione e si eseguono test automatici su di essa. Questi test dovrebbero utilizzare un vero database: in memoria, docker, una macchina virtuale o qualche altra distribuzione.

Ma se si utilizza un framework (come Spring Data), testare i repository testerebbe il framework e non proprio la logica dell'applicazione.

Usando un vero database stai testando che stai effettivamente configurando e usando correttamente il framework. Inoltre, potrebbero esserci delle carenze nel framework che vengono rilevate solo durante i test con un database reale (esempio inventato: Spring Data non supporta la versione 9.2 di PostgreSQL).

Scriverei la maggior parte della mia copertura di test contro fonti derise, ma scriverei alcuni test end-to-end per casi d'uso comunemente utilizzati usando un database reale.


Se si tratta di un test unitario, testeresti il ​​framework separatamente dal layer che utilizza il framework. Dovrebbero esserci sempre alcuni test di integrazione dopo aver completato tutti i test unitari.
Denise Skidmore,

2

Il più delle volte, il test del database in memoria è più semplice del deridere. È anche molto più flessibile. Inoltre, verifica che i file di migrazione siano eseguiti correttamente (quando sono presenti file di migrazione).

Vedi questo pseudo codice:

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

Il InMemoryTestnon dipende da come Databaseviene implementato in UserRepositoryal lavoro. Usa semplicemente l' UserRepositoryinterfaccia pubblica ( create) e quindi afferma contro di essa. Quel test non si interromperà se cambi l'implementazione ma è più lento.

Nel frattempo, MockingDBTesttutto dipende da come Databaseviene implementato UserRepository. In effetti, se si modifica l'implementazione ma si fa ancora funzionare in un altro modo, tale test si interromperà.

Il meglio di entrambi i mondi sarebbe usare un falso implementando l' Databaseinterfaccia:

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

Questo è molto più espressivo, più facile da leggere e da capire e non dipende dall'implementazione del database effettivo fatto nei livelli più alti del codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.