Как стать автором
Обновить

MongoDB: магия вне Хогвартса в IT

Уровень сложностиСредний
Время на прочтение9 мин
Количество просмотров3.5K

Привет, Хабр!

Меня зовут Алена Метенева, я работаю старшим инженером по обеспечению качества в компании SM Lab в проекте «Кассы». Я тестирую бэкенд и интеграции и там, где это возможно, автоматизирую тесты на Java. Сегодня я хочу рассказать вам о том, как MongoDB помогает мне с этим процессом.

Что такое MongoDb

Думаю, многие работали с MongoDB (Монга) и знают, что это нереляционная СУБД, которая использует для хранения данных JSON-структуру: вместо таблиц и строк, как в реляционных базах данных, в MongoDB есть коллекции (набор документов, эквивалент таблицы реляционной базы данных) и документы (внутри коллекции они могут отличаться друг от друга размером, содержанием и количеством полей), которые состоят из пар «ключ–значение».

Для чего Монга тестировщику 

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

Что я имею в виду?

Представьте, что вы тестируете интеграцию с другой системой. Если все работает стабильно, то пройти позитивные сценарии будет проще всего. А если вы хотите протестировать кейс, в котором смежная система выдает ошибку 503 (Service Unavailable) – это будет уже сложнее. Хорошо, если вы управляете обеими системами и можете просто перезагрузить одно приложение и попытаться достучаться до него через второе. А если система не ваша? В таком случае принято использовать моки. Но есть и третий вариант: если ваше приложение для подключения к другому берет ссылку из MongoDB, то эту ссылку можно просто подменить, добавив в нее лишние символы, чтобы получить ту самую ошибку 503 или 404 (Not Found), например.

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

Опять же, при ручном изменении данных в Монге высок риск ложных падений тестов из-за того, что тестировщик забывает после вернуть параметры в исходное состояние, и, когда мы начинаем тестировать следующий кейс, можно столкнуться с ошибочным поведением приложения. У меня такое бывало много раз, и я тратила время на расследования, которые выводили меня на мою же ошибку. Тогда я и решила подключить Монгу к автотестам, чтобы предусмотреть сброс настроек в коде.

Как работать с MongoDb из автотестов

Чтобы было интереснее, у моих тестов есть небольшая предыстория, связанная с Гарри Поттером: 

В Хогвартсе наступили темные времена: Долорес Амбридж захватила пост директора. Не все ученики согласны мириться с этим – Отряд Дамблдора готовит заговор, чтобы свергнуть Амбридж и вернуть профессора в замок. Поздно вечером Отряд собирается в Выручай-комнате, чтобы обсудить свои планы. 

Теперь давайте посмотрим, как работает Монга на примере небольшого сервиса. Репозиторий mongoTestingProject объединяет два проекта: сервис hogwarts и проект с автотестами hogwartsApiTests. В гитхабе есть все инструкции по локальному запуску проекта, так что можете клонировать его себе и работать с MongoDB.

Итак, сервис – это  Хогвартс в миниатюре. У него есть две коллекции – Users и Constant, которые хранятся в MongoDB. Юзеры – это обитатели Хогвартса: студенты и учителя. В константах лежат адреса для подключений, таймауты и фича-тоглы. И еще у сервиса есть полный CRUD для работы с пользователями и константами, а также разные логические методы.

Для того, чтобы начать работать с MongoDB из автотестов, достаточно трех шагов:

Шаг 1. Добавление зависимости в pom.xml

<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>5.2.1</version>
</dependency>

mongodb-driver-sync – это Java-драйвер, позволяющий работать с коллекциями и документами в Монге. 

Шаг 2. Создание клиента 

Для этого нужна строка подключения к вашей БД:

MongoClient mongoClient = MongoClients.create(“mongodb://admin:phKx3UnU6svK8U9C@localhost:27019/?authSource=hogwarts”);

Шаг 3. Подключение к базе данных

MongoDatabase hogwartsDb = mongoClient.getDatabase(“hogwarts”);

Теперь вы готовы работать с Монгой из автотестов :)

В своих автотестах я ищу в БД коллекцию по ее названию, потом фильтрую по названию константы (поле name) и последним шагом изменяю значение (поле value). Как это выглядит:

1. Для поиска коллекции в БД я использую метод getCollection():

public MongoCollection<Document> getCollectionByName(String collectionName) {
        return hogwartsDb.getCollection(collectionName);
    }

2. Для поиска нужной константы в коллекции необходимо создать BSON-фильтр. BSON (Binary JSON) – это формат, который оперирует данными в двоичном формате. MongoDb хранит информацию именно в BSON-формате. Я фильтрую коллекцию по полю name, поэтому его и передаю в фильтр:

Bson filter = eq("name", name);

 В итоге я получаю значение константы:

public String getConstantValueByName(String collectionName, String name) throws JsonProcessingException {
        MongoCollection<Document> collection = getCollectionByName(collectionName);
        Bson filter = eq("name", name);
        String json = Objects.requireNonNull(collection.find(filter).first()).toJson();
        return readValue(json).getValue();
    }

3. После фильтрации коллекции я получаю документ. Для того, чтобы изменить значение константы, мне нужно немного усложнить метод. Монга устроена таким образом, что в ней нельзя обновить одно поле – всегда нужно обновлять целый документ. Поэтому нам нужны DTO-классы для сохранения нашего документа в виде Java-объекта. У объекта я меняю значение поля, потом сериализую весь объект обратно в формат Document, использую метод replaceOne() и уже готовый фильтр, чтобы заменить объект в коллекции:

public UpdateResult changeConstantByName(String configName, String newConfigValue) throws JsonProcessingException {
        MongoCollection<Document> constantCollection = getCollectionByName("constant");
        Bson filter = eq("name", configName);
        String json = Objects.requireNonNull(constantCollection.find(filter)
                                             .first()).toJson();
        
  //Сохраняем документ в виде Java-объекта
        ConstantMongoDbDto constant = readValue(json);
        //Задаем новое значение для константы
        constant.setValue(newConfigValue);
        //Сериализуем объект в JSON, потом в Document
        String constantJson = new Gson().toJson(constant);
        Document newDocument = Document.parse(constantJson);
  
        //Заменяем документ с константой в коллекции
        return getCollectionByName("constant").replaceOne(filter, newDocument);
    }

Далее я пишу небольшой junit extension, в который передаю:

  • название константы; 

  • значение, которое я хочу ей присвоить перед тестом;

  • значение, которое я хочу вернуть ей после теста.

Аннотацию можно посмотреть здесь, а сам Extension – здесь.

А теперь вернемся к нашей истории.

После встречи ученики готовятся расходиться по спальням, но тут в коридоре появляется Филч. Срочно нужно погасить свет в коридоре и спрятаться! 

За выключение света отвечает параметр isLightOn. Если свет не выключить, то Филч поймает всех студентов, что и показано в первом тесте.

Перед тестом я устанавливаю значение константы = true. Потом использую метод, который «прячет» студентов от Филча, т.е. присваивает каждому юзеру параметр isHidden = true. Но этот метод не работает, если свет включен. Как результат, Филч ловит всех заговорщиков:

@DisplayName("Проверка, что Филч поймал всех студентов")
@Test
@ChangeConstant(
            constantName = "isLightOn",
            beforeTestConstantName = "true",
            afterTestConstantName = "false"
    )
    void checkIfFilchCaughtAllStudentsTest() {
        reloadUsers();
        //Студенты пытаются спрятаться при включенном свете
        logicMethods.hideOrCatchStudents(Logical.HIDE_STUDENTS.getValue());
        //Филч ловит студентов
        List<UserDto> users = logicMethods.hideOrCatchStudents(Logical.CATCH_STUDENTS.getValue());

        softAssertions.assertThat(users.size())
          .as("Филч должен поймать всех студентов")
          .isEqualTo(4);
        softAssertions.assertThat(users.stream()
                        .map(UserDto::getName).toList())
                .contains(StudentNames.HARRY_POTTER.getName())
                .contains(StudentNames.GERMIONE_GRANGER.getName())
                .contains(StudentNames.RONALD_WISLEY.getName())
                .contains(StudentNames.NEVILL_LONGBOTTOM.getName());
    }

Но если мы для того же самого теста поменяем значение константы на false, то результат будет другой – почти все студенты смогут спрятаться, Филчу попадется только один рандомный студент:

@DisplayName("Проверка, что Филч поймал только одного из студентов")
@Test
@ChangeConstant(
            constantName = "isLightOn",
            beforeTestConstantName = "false",
            afterTestConstantName = "false"
    )
    void checkIfFilchCaughtOnlyOneStudentTest() {
        reloadUsers();
        //Студенты пытаются спрятаться при выключенном свете
        logicMethods.hideOrCatchStudents(Logical.HIDE_STUDENTS.getValue());
        //Филч ловит только одного студента
        List<UserDto> users = logicMethods.hideOrCatchStudents(Logical.CATCH_STUDENTS.getValue());
        
      softAssertions.assertThat(users.size())
          .as("Филч должен поймать только одного студента")
          .isEqualTo(1);
        softAssertions.assertThat(users.getFirst().getRole())
           .isEqualTo(UserRoles.STUDENT.getRole());
    }

В моем репозитории вы можете увидеть еще несколько тестов, которые «рассказывают» историю об успешном изгнании Амбридж из Школы Чародейства и Волшебства с помощью работы MongoDb. 

Например, есть тест, который показывает, сколько членов Отряда Дамблдора выдаст ученик, которого поймал Филч. И зависеть это будет от того, насколько крепкую сыворотку правды даст ему профессор Снейп.

А последний тест «создает» заклинание, которое возвращает Дамблдора на пост директора школы, а Долорес Амбридж отправляет в тюрьму)) 

Примерно так я и использую MongoDb в своих рабочих автотестах. Правда, без Гарри Поттера :)) Конечно, для этого нужно, чтобы ваше приложение работало с Монгой точно таким же способом: хранило в ней какие-то параметры и считывало их в рантайме. В этом случае можно очень облегчить себе тестирование, подключив в автотесты библиотеку для работы с Монгой. Но даже если вы храните в Монге обычные данные, вы сможете искать их или менять с помощью автотестов и тех методов, которые я вам показала.

Итак, что в итоге

Мы убедились в том, что MongoDb – это отличный инструмент, которым может пользоваться и ручной тестировщик, и автоматизатор. Автоматизация с Монгой помогает расширить тестовое покрытие и застраховаться от ложных падений тестов. Ну и, надеюсь, что я убедила вас в том, что тестировать бэкенд – это весело. Особенно, если подключить фантазию :)  

Теги:
Хабы:
Всего голосов 14: ↑11 и ↓3+8
Комментарии2

Публикации

Информация

Сайт
см-лаб.рф
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Россия
Представитель
Алина Айсина