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 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 véritable 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 quelques tests de bout en bout pour des cas d'utilisation couramment utilisés en utilisant une vraie base de données.
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]');
}
}
InMemoryTest
ne dépend pas de la façon dont Database
est implémenté dans UserRepository
pour fonctionner. Il utilise simplement l'interface publique UserRepository
(create
), puis l'affirme. Ce test ne se cassera pas si vous modifiez l'implémentation mais il est plus lent.
Pendant ce temps, MockingDBTest
repose entièrement sur la façon dont Database
est implémenté dans UserRepository
. En fait, si vous modifiez l'implémentation tout en la faisant fonctionner d'une autre manière, ce test serait rompu.
Le meilleur des deux mondes serait d'utiliser un faux implémentant l'interface Database
:
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.