Целью данной статьи является демонстрация возможности BDD-тестирования веб-сервиса с использованием Docker и JBehave.

Весь исходный код приведен здесь.

Подготовка

Ставим Docker, но до этого — разрешаем использование виртуализации в BIOS, в Windows — доустанавливаем дополнительно Hyper-V, и апдейтим ее до версии, которую просит Docker.

Структура приложения

Приложение представляет собой мультимодульное maven-приложение, со следующими модулями:
  • spring-boot-empty-project — собственно spring-boot приложение
  • dockerization — модуль, оборачивающий spring-boot приложение в Docker-образ
  • jbehave-testing — модуль BDD-тестирования

Немного подробнее:

Сервис

Обычный spring-boot сервис, с контроллером, возвращающим «Hello World!» для домашней страницы:

@RestController
public class Controller {
    @RequestMapping("/")
    @ResponseBody
    String home() {
        return "Hello World!";
    }
}

Оборачивание сервиса в Docker-образ

Тут используем docker-maven-plugin от Spotify и java:8u111-jre-alpine образ в качестве базового образа. Чтобы приложение стартовало после старта образа — используем секцию entryPoint конфигурации плагина:

<!-- *** Build images *** -->
<execution>
  <id>build-spring-boot-simple-app-image</id>
  <phase>package</phase>
  <goals>
    <goal>build</goal>
  </goals>
  <configuration>
    <imageName>jbehave-spring-boot-simple-app-image:${project.version}</imageName>
    <baseImage>java:8u111-jre-alpine</baseImage>
    <!-- run application when container start -->
    <entryPoint>["java", "-jar", "/spring-boot-empty-project-${project.version}.jar"]</entryPoint>
    <!-- copy the service's jar file from target into the root directory of the image -->
    <resources>
      <resource>
	<targetPath>/</targetPath>
	<directory>${project.build.directory}/lib</directory>
	 <include>spring-boot-empty-project-${project.version}.jar</include>
      </resource>
    </resources>
</configuration>
</execution>

Jar-файл с сервисом берется из папки target/lib. Для того, чтобы он там оказался — копируем его туда при помощи maven-dependency-plugin:
<execution>
  <id>copy</id>
  <phase>package</phase>
  <goals>
    <goal>copy</goal>
  </goals>
  <configuration>
    <artifactItems>
      <artifactItem>
        <groupId>by.andd3dfx</groupId>
        <artifactId>spring-boot-empty-project</artifactId>
        <version>${project.version}</version>
        <outputDirectory>${project.build.directory}/lib</outputDirectory>
      </artifactItem>
    </artifactItems>
  </configuration>
</execution>

Поэтому при каждом билде после изменений в сервисе будет браться актуальная версия сервиса.

В той же фазе package тегаем созданный образ:

<!-- *** Tag images *** -->
<execution>
  <id>tag-spring-boot-simple-app-image</id>
  <phase>package</phase>
  <goals>
    <goal>tag</goal>
  </goals>
  <configuration>
    <image>jbehave-spring-boot-simple-app-image:${project.version}</image>
    <newName>${dockerRepository}/jbehave-spring-boot-simple-app-image:${project.version}</newName>
  </configuration>
</execution>

В фазе push пушаем образ в репозиторий:

<!-- *** Push images *** -->
<execution>
  <id>push-spring-boot-simple-app-image</id>
  <phase>deploy</phase>
  <goals>
    <goal>push</goal>
  </goals>
  <configuration>
  <imageName>${dockerRepository}/jbehave-spring-boot-simple-app-image:${project.version}</imageName>
  </configuration>
</execution>

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

Собственно BDD-тестирование

Описываем историю в story-файле:

Narrative:
As a developer
I want to get access to spring-boot application from outside of docker container

Scenario: After when Docker container with spring-boot app in it started, application home page is accessible

Then spring-boot application home page accessible

— просто хотим, чтобы домашняя страница была доступна, чего же болей

В соответствии с именем story-файла создаем java-класс с именем GetAccessToSpringBootAppFromOutsideOfDockerContainer:

public class GetAccessToSpringBootAppFromOutsideOfDockerContainer extends JUnitStory {
    // Here we specify the configuration, starting from default MostUsefulConfiguration,
    // and changing only what is needed
    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration()
          // where to find the stories
          .useStoryLoader(new LoadFromClasspath(this.getClass()))
          // CONSOLE and TXT reporting
          .useStoryReporterBuilder(new StoryReporterBuilder().withDefaultFormats()
          .withFormats(Format.CONSOLE, Format.TXT));
    }

    // Here we specify the steps classes
    @Override
    public InjectableStepsFactory stepsFactory() {
        // varargs, can have more that one steps classes
        return new InstanceStepsFactory(configuration(), new CheckSpringBootAppHomePageAvailabilitySteps());
    }
}

В это классе используется другой класс, специфицирующий шаги тестирования:

public class CheckSpringBootAppHomePageAvailabilitySteps {
    @Then("spring-boot application home page accessible")
    public void checkExistenceOfNewRecordsInDB() throws IOException {
        String baseUrl = System.getProperty("docker.url");
        RestTemplate restTemplate = new RestTemplate();
        String result =
                restTemplate.getForObject(
                        baseUrl,
                        String.class
                );
        assertThat("Default 'Hello World!' string expected", result, is("Hello World!"));
    }
}

Чтобы получить docker.url из java-кода, добавляем это в конфигурацию maven-failsafe плагина:

<configuration>
  <systemPropertyVariables>
    <docker.url>http://${docker.host.address}:${spring-boot-app.port}</docker.url>
  </systemPropertyVariables>
</configuration>

Чтобы протестировать приготовленный Docker-образ с сервисом внутри — стартуем образ в фазе pre-integration-test и стопаем в фазе post-integration-test при помощи docker-maven-plugin от io.fabric8:

<!-- Hooking into the lifecycle -->
<executions>
  <execution>
    <id>start</id>
    <phase>pre-integration-test</phase>
    <goals>
      <goal>start</goal>
    </goals>
  </execution>
  <execution>
    <id>stop</id>
    <phase>post-integration-test</phase>
    <goals>
      <goal>stop</goal>
    </goals>
  </execution>
</executions>

Также задаем таймаут для старта сервиса и префикс TC для отслеживания сообщений от Docker-образа в логах (fabric8 плагин как раз позволяет сделать это):

<!-- ............................................................... -->
<!-- Runtime configuration for starting/stopping/linking containers -->
<!-- ............................................................... -->
<run>
  <!-- Assign dynamically mapped ports to maven variables (which can be reused in integration tests) -->
  <ports>
    <port>spring-boot-app.port:8090</port>
  </ports>
  <wait>
    <!-- Check for this URL to return a 200 return code .... -->
    <url>
      http://${docker.host.address}:${spring-boot-app.port}
    </url>
    <!-- ... but at max 10 seconds -->
    <time>10000</time>
  </wait>
  <log>
    <prefix>TC</prefix>
    <color>cyan</color>
  </log>
</run>

При запуске билда видим, что история тестируется успешно:

Running story stories/get_access_to_spring_boot_app_from_outside_of_docker_container.story

(stories/get_access_to_spring_boot_app_from_outside_of_docker_container.story)
Scenario: After when Docker container with spring-boot app in it started, application home page is accessible
...
Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

— собственно чего и хотелось бы.

Напоследок вопрос к комьюнити:
Интересны ли будут результаты экспериментов по интеграции Docker в Maven build при помощи различных методов?

Проголосовало 24 человека. Воздержалось 9 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Поделиться с друзьями
-->

Комментарии (7)


  1. relgames
    19.05.2017 09:42

    А зачем докер? Можно ведь просто запустить сервис. У спринга есть режим integration testing, когда поднимается веб сервер на рандомном порту.


    1. andd3dfx
      19.05.2017 10:28
      -1

      Да, можно. Но с помощью докера вы можете создать для сервиса изолированное окружение с нужными вам настройками, которые будут одни и те же при каждом тестировании и не будете зависеть от текущей конфигурации сервера


      1. relgames
        19.05.2017 19:37
        +2

        В общем случае — конечно.
        В этот конкретном случае смысла нет, мне кажется.


        1. andd3dfx
          19.05.2017 21:57

          Это прототип. Не замусоренный (пока еще) бизнес-логикой :)


      1. sshikov
        19.05.2017 21:01

        Одни и теже настройки? Для этого нужно, чтобы они строились автоматически. И если это условие выполнено — докер тут совершенно лишний. Правильно написанный спринговый веб сервис и так не зависит почти ни от чего.


        1. andd3dfx
          19.05.2017 22:08

          Можно поднатужиться и придумать ситуацию когда это нужно: например на сервере одна версия java, а вам надо тестировать этот сервис на другой или еще что-то подобное. Если вы захотите — уверен сможете придумать и другие примеры.
          Но если у вас нет потребности в использовании докера — то да, вам оно не надо


          1. sshikov
            19.05.2017 22:27

            В общем случае — да. Но в конкретном это за уши притянуто, прямо скажем.


            Да и с версиями вы тоже скорее всего перебарщиваете — чтобы иметь разные версии java, вам нужно будет иметь разные докер-контейнеры. И если они у вас нестандартные хоть чуть-чуть — у вас будут сложности, чтобы их собрать. Докеровский путь для этого далеко не такой гибкий и удобный, как хотелось бы. Я бы сказал, что это окупится в лучшем случае, если нужны несколько сервисов. Типа базы данных, веб сервиса и т.п.