Целью данной статьи является демонстрация возможности BDD-тестирования веб-сервиса с использованием Docker и JBehave.
Весь исходный код приведен здесь.
Подготовка
Ставим Docker, но до этого — разрешаем использование виртуализации в BIOS, в Windows — доустанавливаем дополнительно Hyper-V, и апдейтим ее до версии, которую просит Docker.
Структура приложения
Приложение представляет собой мультимодульное maven-приложение, со следующими модулями:
Немного подробнее:
Сервис
Обычный spring-boot сервис, с контроллером, возвращающим «Hello World!» для домашней страницы:
Оборачивание сервиса в Docker-образ
Тут используем docker-maven-plugin от Spotify и java:8u111-jre-alpine образ в качестве базового образа. Чтобы приложение стартовало после старта образа — используем секцию entryPoint конфигурации плагина:
Jar-файл с сервисом берется из папки target/lib. Для того, чтобы он там оказался — копируем его туда при помощи maven-dependency-plugin:
Поэтому при каждом билде после изменений в сервисе будет браться актуальная версия сервиса.
В той же фазе package тегаем созданный образ:
В фазе push пушаем образ в репозиторий:
Пуш образа — операция, занимающая некоторое время, поэтому в ходе обычного билда образ лишь создается и помещается в локальный репозиторий. Но в процессе релиза — он будет также пушнут в ремоутный репозиторий
Собственно BDD-тестирование
Описываем историю в story-файле:
— просто хотим, чтобы домашняя страница была доступна, чего же болей
В соответствии с именем story-файла создаем java-класс с именем GetAccessToSpringBootAppFromOutsideOfDockerContainer:
В это классе используется другой класс, специфицирующий шаги тестирования:
Чтобы получить docker.url из java-кода, добавляем это в конфигурацию maven-failsafe плагина:
Чтобы протестировать приготовленный Docker-образ с сервисом внутри — стартуем образ в фазе pre-integration-test и стопаем в фазе post-integration-test при помощи docker-maven-plugin от io.fabric8:
Также задаем таймаут для старта сервиса и префикс TC для отслеживания сообщений от Docker-образа в логах (fabric8 плагин как раз позволяет сделать это):
При запуске билда видим, что история тестируется успешно:
— собственно чего и хотелось бы.
Напоследок вопрос к комьюнити:
Весь исходный код приведен здесь.
Подготовка
Ставим 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
— собственно чего и хотелось бы.
Напоследок вопрос к комьюнити:
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Поделиться с друзьями
relgames
А зачем докер? Можно ведь просто запустить сервис. У спринга есть режим integration testing, когда поднимается веб сервер на рандомном порту.
andd3dfx
Да, можно. Но с помощью докера вы можете создать для сервиса изолированное окружение с нужными вам настройками, которые будут одни и те же при каждом тестировании и не будете зависеть от текущей конфигурации сервера
relgames
В общем случае — конечно.
В этот конкретном случае смысла нет, мне кажется.
andd3dfx
Это прототип. Не замусоренный (пока еще) бизнес-логикой :)
sshikov
Одни и теже настройки? Для этого нужно, чтобы они строились автоматически. И если это условие выполнено — докер тут совершенно лишний. Правильно написанный спринговый веб сервис и так не зависит почти ни от чего.
andd3dfx
Можно поднатужиться и придумать ситуацию когда это нужно: например на сервере одна версия java, а вам надо тестировать этот сервис на другой или еще что-то подобное. Если вы захотите — уверен сможете придумать и другие примеры.
Но если у вас нет потребности в использовании докера — то да, вам оно не надо
sshikov
В общем случае — да. Но в конкретном это за уши притянуто, прямо скажем.
Да и с версиями вы тоже скорее всего перебарщиваете — чтобы иметь разные версии java, вам нужно будет иметь разные докер-контейнеры. И если они у вас нестандартные хоть чуть-чуть — у вас будут сложности, чтобы их собрать. Докеровский путь для этого далеко не такой гибкий и удобный, как хотелось бы. Я бы сказал, что это окупится в лучшем случае, если нужны несколько сервисов. Типа базы данных, веб сервиса и т.п.