Как нам помог Docker в написании тестов

Чтобы показывать вам рекламу — мы в GetIntent должны быть уверены в устойчивой и безотказной работе нашей рекламной платформы. Надежность системы складывается из многих компонентов: тип используемого железа, системная/сетевая конфигурация и архитектура приложения. Внесение изменений в достаточно сложные, распределенные приложения всегда несет в себе риск.
Разработчики со своей стороны пытаются минимизировать эти риски и пишут тесты: unit и интеграционные. Написание unit тестов обычно не составляет каких-либо трудностей. С интеграционными тестами, в зависимости от их изощренности, ситуация сложнее.

27622eeca03b49b5a5c7e10ce76edc26.png


Когда тесты используют Tomcat или Jetty это не доставляет никаких проблем: эти сервера написаны на java и могут легко встраиваться в тесты. Но, например, мы используем Aerospike и когда мы хотим протестировать взаимодействие с этой базой данных, нас ожидают следующие трудности:

  • Aerospike написан не на Java и не может быть легко встроен в наше приложение.
  • Хочется чтобы разработчик мог запустить тесты на всех популярных платформах: Windows, OS X или Linux. Aerospike же предоставляет бинарники только для Linux.
  • Тесты могут выполняться параллельно, следовательно нам нужно несколько серверов?
  • Каждый тест должен получать в распоряжение чистый экземпляр базы данных.


Часто для этих целей используется общее тестовое окружение, например удаленный сервер, на котором уже настроен Aerospike и на котором можно запустить некоторые тесты. Однако, подход обладает определенными недостатками:

  • Запуск тестов на удаленном сервере занимает существенно больше времени, чем запуск локальных тестов. Это особенно заметно при работе с медленным интернетом.
  • Возникают проблемы с изоляцией, в случае, если несколько разработчиков решили одновременно протестировать приложение.
  • Довольно часто политика информационной безопасности компании требует прятать сервера за корпоративным VPN. Перспектива настройки VPN клиента может отвадить все желание работать из дома.


Альтернативный вариант: требовать подготовить локальное окружение для тестов, писать длинные инструкции по установке и настройке Aerospike для трех платформ (Win/Mac/Linux). Но есть и другой вариант — воспользоваться утилитами автоматизации, такими, как Docker.

Docker — это система развертывания и управления приложениями в изолированной среде (контейнерах). Он построен на принципах клиент-серверной архитектуры, docker client есть для всех основных OC, а docker daemon работает только на Linux — системах. Однако это не проблема: с помощью docker-machine можно запустить docker и на Windows/OS X (правда на виртуальном хосте). Итак, чтобы запустить тесты разработчику необходимо иметь на машине настроенный docker client — проверить это можно командой: docker run hello-world. Для Windows и OS X потребуется установить docker-machine.

Embedded aerospike


Для использования Aerospike в интеграционных тестах, мы написали обертку для docker и docker-machine. Она умеет:

  1. Стартовать / останавливать контейнеры.
  2. Смонтировать конфигурационный файл aerospike.conf внутрь контейнера.
  3. Привязать порт контейнера к свободному порту хоста.
  4. Стартовать / останавливать Docker Machine если тесты запускаются на Windows/OS X.


Для управления контейнерами мы используем docker-java API client — популярный Java API клиент для Docker. Чтобы запустить сервер Aerospike внутри Docker контейнера, настроить проброс портов и смонтировать конфигурационный файл, нужно выполнить команду:

docker run -d -P -p 3000:3000 -v
path/to/aerospike.conf:/etc/aerospike/aerospike.conf --name aerospike aerospike


А вот код, который делает тоже самое, используя Docker Remote Api


    ExposedPort tcp3000 = ExposedPort.tcp(3000);
    Volume volume = new Volume("/etc/aerospike/aerospike.conf");
    Ports portBindings = new Ports();
    portBindings.bind(tcp3000, Ports.binding(aerospikePort));
    CreateContainerResponse container = dockerClient.createContainerCmd(IMAGE_ID)
          .withExposedPorts(tcp3000)
          .withPortBindings(portBindings)
          .withBinds(new Bind(aerospikeConfPath, volume, AccessMode.ro))
          .exec();

    dockerClient.startContainerCmd(container.getId())
          .exec();


Более детально можно посмотреть в классе AerospikeServer.

Пишем тест


Давайте рассмотрим пример интеграционного теста с использованием embedded-aerospike. Допустим у нас есть класс SimpleAerospikeClient, который умеет хранить и получать сегменты пользователя по идентификатору.

    public Set getSegments(Long userId);
    public void addSegments(Long userId, Set segments);


Посмотреть полный класс.

Пишем тест, который проверяет корректность реализации данных методов.

Сначала надо настроить и запустить сервер.

    @BeforeMethod
    public void setUp() throws Exception {
        aerospikeServer = AerospikeServer.builder()
                .aerospikeConfPath(getClass().getResource("/aerospike.conf").getFile())
                .dockerConfig(DockerClientConfig.createDefaultConfigBuilder().build())
                .build();
        aerospikeServer.start();
    }


Проверяем, что данные правильно записываются и читаются из базы данных.

      @Test
      public void test() {
        long userId = ThreadLocalRandom.current().nextLong();
        aerospikeClient.addSegments(userId, new HashSet() {{
            add(150);
            add(151);
        }});
        Set segments = aerospikeClient.getSegments(userId);
        Assert.assertEquals(segments.size(), 2);
        Assert.assertTrue(segments.contains(150));
        Assert.assertTrue(segments.contains(151));
    }


Не забываем остановить и удалить контейнеры, которые были созданы во время тестов.

    @AfterMethod
    public void tearDown() throws Exception {
        aerospikeServer.stop();
    }


Заключение


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

© Habrahabr.ru