Test - DB en mémoire vs mocking

12

Lors de l'écriture de tests, pourquoi quelqu'un voudrait-il utiliser une base de données en mémoire plutôt que de se moquer des données?

Je pouvais voir que les bases de données en mémoire pouvaient être utiles pour tester ses référentiels. Mais si vous utilisez un framework (comme Spring Data), tester les référentiels serait tester le framework et pas vraiment la logique de l'application.

La simulation, cependant, semble plus rapide et suit le même schéma que celui généralement utilisé lors de l'écriture de tests unitaires et de TDD.

Alors qu'est-ce qui me manque? Quand / pourquoi une base de données en mémoire serait-elle avantageuse?


la source

Réponses:

14

La simulation est la solution idéale pour les tests unitaires, et elle peut également être utilisée pour les tests d'intégration pour améliorer la vitesse, mais elle n'offre pas le même niveau de confiance que lorsque vous utilisez une base de données en mémoire. Vous devez écrire des tests de bout en bout dans lesquels vous configurez l'application entière aussi près que possible de la façon dont elle est configurée en production et exécutez des tests automatisés par rapport à celle-ci. Ces tests doivent utiliser une vraie base de données - en mémoire, docker, une machine virtuelle ou un autre déploiement.

Mais si vous utilisez un framework (comme Spring Data), tester les référentiels serait tester le framework et pas vraiment la logique de l'application.

En utilisant une vraie base de données, vous testez que vous configurez et utilisez correctement le framework. De plus, il peut y avoir des lacunes dans le cadre qui ne sont révélées que lors des tests avec une base de données réelle (exemple artificiel: Spring Data ne prend pas en charge la version 9.2 de PostgreSQL).

J'écrirais la majeure partie de ma couverture de test contre des sources simulées, mais j'écrirais des tests de bout en bout pour des cas d'utilisation couramment utilisés en utilisant une vraie base de données.

Samuel
la source
S'il s'agit d'un test unitaire, vous testeriez le framework séparément de la couche qui utilise le framework. Il devrait toujours y avoir des tests d'intégration après tous les tests unitaires.
Denise Skidmore
2

La plupart du temps, les tests de base de données en mémoire sont plus simples que les simulations. C'est aussi beaucoup plus flexible. Et il vérifie également que les fichiers de migration sont bien exécutés (lorsqu'il y a des fichiers de migration).

Voir ce pseudo code:

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

Le InMemoryTestne dépend pas de la façon dont Databaseest mis en œuvre UserRepositorypour fonctionner. Il utilise simplement l' UserRepositoryinterface publique ( create) puis l'affirme. Ce test ne se cassera pas si vous modifiez l'implémentation mais il est plus lent.

Pendant ce temps, le MockingDBTestrepose entièrement sur la façon dont il Databaseest mis en œuvre UserRepository. En fait, si vous modifiez l'implémentation tout en la faisant fonctionner d'une autre manière, ce test échouerait.

Le meilleur des deux mondes serait d'utiliser un faux implémentant l' Databaseinterface:

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

C'est beaucoup plus expressif, plus facile à lire et à comprendre, et cela ne dépend pas de l'implémentation de la base de données réelle effectuée dans les couches supérieures du code.

Steve Chamaillard
la source