Модульное тестирование с MongoDB - PullRequest
56 голосов
/ 14 сентября 2011

Моя база данных - MongoDB.Я пишу API уровня данных, чтобы абстрагировать детали реализации от клиентских приложений, то есть, по сути, я предоставляю один открытый интерфейс (объект, который действует как IDL).

Я тестирую свойлогика, как я иду в манере TDD.Перед каждым модульным тестом вызывается метод @Before для создания одноэлементной базы данных, после чего после завершения теста вызывается метод @After для удаления базы данных.Это помогает продвигать независимость между модульными тестами.

Почти все модульные тесты, то есть , выполняющие контекстный запрос , требуют, чтобы какая-то логика вставки выполнялась заранее.В моем общедоступном интерфейсе есть метод вставки - но, кажется, некорректно использовать этот метод в качестве логики прекурсора для каждого модульного теста.

Действительно мне нужен какой-то механизм насмешки, но у меня пока не было большого опытафальшивые фреймворки, и кажется, что Google не возвращает ничего, кроме фальшивых фреймворков, которые можно использовать с MongoDB.

Что другие делают в этих ситуациях?То есть, как люди тестируют код модуля, который взаимодействует с базой данных?

Кроме того, мой общедоступный интерфейс подключается к базе данных, определенной во внешнем файле конфигурации - кажется неправильным использовать это соединение для моего модульного тестирования - сноваСитуация, которая выиграет от какого-то насмешки?

Ответы [ 5 ]

55 голосов
/ 25 ноября 2011

Технически тесты, которые обращаются к базе данных (nosql или иным образом), не являются модульными тестами , так как тесты проверяют взаимодействие с внешней системой, а не просто тестируют изолированную единицу кода. Однако тесты, которые обращаются к базе данных, часто бывают чрезвычайно полезными и достаточно быстрыми для запуска с другими модульными тестами.

Обычно у меня есть интерфейс службы (например, UserService), который инкапсулирует всю логику работы с базой данных. Код, основанный на UserService, может использовать поддельную версию UserService и легко тестируется.

При тестировании реализации Сервиса, который общается с Mongo (например, MongoUserService), проще всего написать некоторый Java-код, который запустит / остановит процесс Монго на локальной машине, и ваш MongoUserService подключится к этому, посмотрите вопрос к некоторым заметкам .

Вы можете попытаться смоделировать функциональность базы данных во время тестирования MongoUserService, но обычно это слишком подвержено ошибкам и не проверяет то, что вы действительно хотите проверить, то есть взаимодействие с реальной базой данных. Поэтому при написании тестов для MongoUserService вы устанавливаете состояние базы данных для каждого теста. Посмотрите на DbUnit для примера структуры для работы с базой данных.

28 голосов
/ 25 ноября 2011

Как пишет sbridges в этом посте, плохой идеей является отсутствие выделенного сервиса (иногда также известного как репозиторий или DAO), который абстрагирует доступ к данным от логики. Затем вы можете проверить логику, предоставив макет DAO.

Другой подход, который я использую, - создать объект Mock of Mongo (например, PowerMockito), а затем вернуть соответствующие результаты. Это потому, что вам не нужно проверять, работает ли база данных в модульных тестах, но более того, вы должны проверить, был ли правильный запрос отправлен в базу данных.

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

Это тоже будет вариант. Конечно создание макетов и возвращение соответствующих объектов просто закодировано как пример выше.

17 голосов
/ 20 февраля 2013

Я написал поддельную реализацию MongoDB на Java: mongo-java-server

По умолчанию используется бэкэнд в памяти, который можно легко использовать в модульных и интеграционных тестах.

Пример

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();
6 голосов
/ 30 октября 2018

Сегодня я думаю, что наилучшей практикой является использование testcontainers библиотеки (Java) или testcontainers-python порта на Python. Это позволяет использовать образы Docker с юнит-тестами. Чтобы запустить контейнер в коде Java, просто создайте экземпляр объекта GenericContainer ( пример ):

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

или на Python ( пример ):

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)
1 голос
/ 21 марта 2018

Я удивлен, что пока никто не советовал использовать fakemongo . Он очень хорошо эмулирует клиент mongo, и все это работает на той же JVM с тестами - так что интеграционные тесты становятся надежными и технически намного ближе к истинным «модульным тестам», поскольку взаимодействие с внешними системами не происходит. Это похоже на использование встроенной H2 для модульного тестирования вашего кода SQL. Я был очень счастлив использовать fakemongo в модульных тестах, которые тестируют код интеграции базы данных в сквозной манере. Рассмотрим эту конфигурацию в контексте тестовой пружины:

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

}

С этим вы можете протестировать свой код, который использует MongoTemplate из контекста Spring, и в сочетании с nosql-unit , jsonunit и т. Д. Вы получите надежные модульные тесты, которые охватывают монго-запросы код.

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

Я использовал fakemongo без проблем с драйвером mongo 3.4, и сообщество действительно близко к выпуску версии, поддерживающей драйвер 3.6 (https://github.com/fakemongo/fongo/issues/316).

...