Test à l'unité avec MongoDB

ma base de données de choix est MongoDB. J'écris une API de couche de données pour faire abstraction des détails d'implémentation des applications client - c'est-à-dire que je fournis essentiellement une interface publique unique (un objet qui agit comme un IDL).

je teste ma logique en allant D'une manière TDD. Avant chaque test unitaire, une méthode @Before est appelée pour créer une base de données singleton, après quoi, lorsque le test est terminé, une méthode @After est appelée pour supprimer la base de données. Ce aide à promouvoir l'indépendance des tests unitaires.

presque tous les tests unitaires, c'est-à-dire qui effectuent une requête contextuelle , nécessitent une sorte de logique d'insertion pour se produire avant la main. Mon interface publique fournit une méthode insert - pourtant, il semble incorrect d'utiliser cette méthode comme logique précurseur à chaque test unitaire.

J'ai vraiment besoin d'une sorte de mécanisme de moquerie, pourtant, je n'ai pas eu beaucoup d'expérience avec les cadres de moquerie, et il semble que Google ne renvoie rien concernant un cadre moqueur que L'on pourrait utiliser avec MongoDB.

que font les autres dans ces situations? C'est-à-dire, comment les gens unit-ils le code de test qui interagit avec une base de données?

en outre, mon interface publique se connecte à une base de données définie dans un fichier de configuration externe - il semble incorrect d'utiliser cette connexion pour mon test de l'Unité - encore une fois, une situation qui bénéficierait d'une sorte de moquerie?

52
demandé sur wulfgarpro 2011-09-14 13:18:06

4 réponses

comme l'a écrit sbridges dans ce post, c'est une mauvaise idée de ne pas avoir un service dédié (parfois aussi connu sous le nom de dépôt ou DAO) qui simplifie l'accès aux données de la logique. Ensuite, vous pourriez tester la logique en fournissant un mock of the DAO.

une autre approche que je fais est de créer une moquerie de l'objet Mongo (par exemple PowerMockito) et ensuite retourner les résultats appropriés. Cela parce que vous n'avez pas à tester si la base de données fonctionne dans des tests unitaires, mais plus sur vous devrait tester si la bonne requête a été envoyée à la base de données.

Mongo mongo = PowerMockito.mock(Mongo.class);
DB db = PowerMockito.mock(DB.class);
DBCollection dbCollection = PowerMockito.mock(DBCollection.class);

PowerMockito.when(mongo.getDB("foo")).thenReturn(db);
PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection);

MyService svc = new MyService(mongo); // Use some kind of dependency injection
svc.getObjectById(1);

PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));

ce serait aussi une option. Bien sûr, la création des moqueries et le retour des objets appropriés est simplement codé comme un exemple ci-dessus.

26
répondu rit 2011-12-20 05:53:28

techniquement, les tests qui parlent à une base de données (nosql ou autre) ne sont pas des tests unitaires , car les tests testent les interactions avec un système externe, et pas seulement tester une unité de code isolée. Toutefois, les tests qui parlent à une base de données sont souvent extrêmement utiles et sont souvent assez rapides pour être exécutés avec les autres tests unitaires.

D'habitude j'ai une interface de Service (par exemple UserService) qui encapsule toute la logique pour traiter avec le la base de données. Le Code qui repose sur UserService peut utiliser une version moquée de UserService et est facilement testé.

lors de la mise à l'essai de l'implémentation du Service qui parle à Mongo, (par exemple MongoUserService), il est plus facile d'écrire un code java qui va démarrer/arrêter un processus mongo sur la machine locale, et avoir votre MongoUserService connecté à cela, voir cette question pour quelques notes .

vous pourriez essayer de vous moquer de la fonctionnalité de la base de données tout en testant MongoUserService, mais généralement c'est trop sujet aux erreurs, et ne teste pas ce que vous voulez vraiment tester, qui est l'interaction avec une base de données réelle. Ainsi, lorsque vous écrivez des tests pour MongoUserService, vous mettez en place un État de base de données pour chaque test. Regardez DbUnit pour un exemple d'un cadre pour le faire avec une base de données.

44
répondu sbridges 2017-07-11 05:37:09

j'ai écrit une implémentation MongoDB stub en Java: mongo-java-server

Default est un backend en mémoire, qui peut être facilement utilisé dans les tests D'unité et D'intégration.

exemple

MongoServer server = new MongoServer(new MemoryBackend());
// bind on a random local port
InetSocketAddress serverAddress = server.bind();

MongoClient client = new MongoClient(new ServerAddress(serverAddress));

DBCollection coll = client.getDB("testdb").getCollection("testcoll");
// creates the database and collection in memory and inserts the object
coll.insert(new BasicDBObject("key", "value"));

assertEquals(1, collection.count());
assertEquals("value", collection.findOne().get("key"));

client.close();
server.shutdownNow();
14
répondu Benedikt Waldvogel 2013-07-11 11:22:21

je suis surpris que personne n'ait été conseillé d'utiliser fakemongo jusqu'à présent. Il émule assez bien le client mongo, et il fonctionne sur la même JVM avec des tests - donc les tests d'intégration deviennent robustes, et techniquement beaucoup plus proches des vrais "tests unitaires", puisqu'il n'y a pas d'interaction de système étranger. C'est comme utiliser H2 intégré pour tester votre code SQL. J'ai été très heureux d'utiliser fakemongo dans les tests unitaires qui testent le code d'intégration de la base de données de manière end-to-end. Considérez ceci configuration dans le contexte du ressort d'essai:

@Configuration
@Slf4j
public class FongoConfig extends AbstractMongoConfiguration {
    @Override
    public String getDatabaseName() {
        return "mongo-test";
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        log.info("Creating Fake Mongo instance");
        return new Fongo("mongo-test").getMongo();
    }

    @Bean
    @Override
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), getDatabaseName());
    }

}

avec ceci vous pouvez tester votre code qui utilise MongoTemplate du contexte de ressort, et en combinaison avec nosql-unit , jsonunit , etc. vous obtenez des tests unitaires robustes qui couvrent le code d'interrogation de mongo.

@Test
@UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
@DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"})
public void shouldCleanUploadSubjectCollection() throws Exception {
    //given
    JobParameters jobParameters = new JobParametersBuilder()
            .addString("studyId", "TSDR1326")
            .addString("execId", UUID.randomUUID().toString())
            .toJobParameters();

    //when
    //next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo
    final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters);

    //then
    assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
    final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")),
            DBObject.class, "subject").toString();

    assertThatJson(resultJson).isArray().ofLength(3);
    assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime());

    assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01");
    assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime());

    ... etc
}

j'ai utilisé fakemongo sans problèmes avec le pilote mongo 3.4, et la communauté est vraiment proche de publier une version qui prend en charge 3.6 pilote ( https://github.com/fakemongo/fongo/issues/316 ).

1
répondu int21h 2018-03-20 21:38:45