it-swarm.it

Test unitari con MongoDB

Il mio database preferito è MongoDB. Sto scrivendo un'API a livello di dati per sottrarre i dettagli dell'implementazione dalle applicazioni client, ovvero essenzialmente sto fornendo un'unica interfaccia pubblica (un oggetto che funge da IDL).

Sto testando la mia logica mentre vado in maniera TDD. Prima di ogni unit test, viene chiamato un metodo @Before Per creare un singleton di database, dopodiché, al termine del test, viene chiamato un metodo @After Per eliminare il database. Questo aiuta a promuovere l'indipendenza tra i test unitari.

Quasi tutti i test unitari, ovvero esecuzione di una query contestuale, richiedono che prima si verifichi un qualche tipo di logica di inserimento. La mia interfaccia pubblica fornisce un metodo insert - tuttavia, sembra errato utilizzare questo metodo come logica precursore per ogni test unitario.

In realtà ho bisogno di una sorta di meccanismo di derisione, tuttavia, non ho avuto molta esperienza con i framework di derisione e sembra che Google non restituisca nulla in merito a un framework di derisione che si potrebbe usare con MongoDB.

Cosa fanno gli altri in queste situazioni? Cioè, in che modo il codice unit test delle persone che interagisce con un database?

Inoltre, la mia interfaccia pubblica si collega a un database definito in un file di configurazione esterno - sembra errato utilizzare questa connessione per il test dell'unità - di nuovo, una situazione che trarrebbe beneficio da una sorta di derisione?

58
wulfgarpro

Come sbridges ha scritto in questo post, è una cattiva idea non avere un servizio dedicato (a volte noto anche come repository o DAO) che impedisce l'accesso ai dati dalla logica. Quindi è possibile testare la logica fornendo una simulazione del DAO.

Un altro approccio che faccio è creare un oggetto Mock of Mongo (ad esempio PowerMockito) e quindi restituire i risultati appropriati. Questo perché non è necessario verificare se il database funziona in unit test, ma più oltre si dovrebbe verificare se la query corretta è stata inviata al database.

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

Sarebbe anche un'opzione. Naturalmente la creazione delle beffe e il ritorno degli oggetti appropriati è appena codificata come esempio sopra.

29
rit

I test tecnici che parlano a un database (nosql o altro) non sono nit test , poiché i test stanno testando le interazioni con un sistema esterno e non solo testando un'unità di codice isolata. Tuttavia, i test che parlano con un database sono spesso estremamente utili e spesso sono abbastanza veloci da essere eseguiti con gli altri test unitari.

Di solito ho un'interfaccia di servizio (ad es. UserService) che incapsula tutta la logica per gestire il database. Il codice che si basa su UserService può utilizzare una versione derisa di UserService ed è facilmente testato.

Quando si verifica l'implementazione del servizio che comunica con Mongo, (ad esempio MongoUserService) è più semplice scrivere un codice Java che avvii/fermi un processo mongo sul computer locale e abbia MongoUserService connettersi a questo, vedere questo domanda per alcune note .

Potresti provare a deridere la funzionalità del database durante il test di MongoUserService, ma in genere è troppo soggetto a errori e non verifica ciò che desideri veramente testare, ovvero l'interazione con un database reale. Pertanto, quando si scrivono test per MongoUserService, si imposta uno stato del database per ciascun test. Guarda DbUnit per un esempio di un framework per farlo con un database.

61
sbridges

Ho scritto un'implementazione falsa MongoDB in Java: mongo-Java-server

L'impostazione predefinita è un back-end in memoria, che può essere facilmente utilizzato nei test Unit e Integration.

Esempio

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();
17

Oggi penso che la migliore pratica sia usare la porta testcontainers library (Java) o testcontainers-python su Python. Permette di usare immagini Docker con unit test. Per eseguire il contenitore in Java solo un'istanza dell'oggetto GenericContainer ( esempio ):

GenericContainer mongo = new GenericContainer("mongo:latest")
    .withExposedPorts(27017);

MongoClient mongoClient = new MongoClient(mongo.getContainerIpAddress(), mongo.getMappedPort(27017));
MongoDatabase database = mongoClient.getDatabase("test");
MongoCollection<Document> collection = database.getCollection("testCollection");

Document doc = new Document("name", "foo")
        .append("value", 1);
collection.insertOne(doc);

Document doc2 = collection.find(new Document("name", "foo")).first();
assertEquals("A record can be inserted into and retrieved from MongoDB", 1, doc2.get("value"));

o on Python ( esempio ):

mongo = GenericContainer('mongo:latest')
mongo.with_bind_ports(27017, 27017)

with mongo_container:
    def connect():
        return MongoClient("mongodb://{}:{}".format(mongo.get_container_Host_ip(),
                                                    mongo.get_exposed_port(27017)))

    db = wait_for(connect).primer
    result = db.restaurants.insert_one(
        # JSON as dict object
    )

    cursor = db.restaurants.find({"field": "value"})
    for document in cursor:
        print(document)
7
Eugene Lopatkin

Sono sorpreso che nessuno abbia consigliato di usare fakemongo finora. Emula abbastanza bene il client mongo e funziona tutti sullo stesso JVM con i test - quindi i test di integrazione diventano robusti e tecnicamente molto più vicini ai veri "test unitari", dal momento che nessuna interazione con sistemi esterni ha luogo. È come usare H2 incorporato per testare l'unità del tuo codice SQL. Sono stato molto contento di aver usato fakemongo in unit test che testano il codice di integrazione del database in maniera end-to-end. Considera questa configurazione nel contesto della primavera di prova:

@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());
    }

}

Con questo puoi testare il tuo codice che utilizza MongoTemplate dal contesto primaverile e in combinazione con nosql-unit , jsonunit , ecc. Ottieni test unitari robusti che coprono il codice di query 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
}

Ho usato fakemongo senza problemi con il driver mongo 3.4 e la community è davvero vicina a rilasciare una versione che supporta il driver 3.6 ( https://github.com/fakemongo/fongo/issues/316 ).

1
int21h