Статьи

Интеграционное тестирование с Maven и Docker

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

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

Мой личный вклад в эту тему — показать вам возможный рабочий процесс, который позволяет запускать и останавливать контейнеры Docker из задания Maven.

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

Обычные стратегии для проверки этих взаимодействий:

  • В серверах памяти; реализованы в Java, которые обычно очень быстрые, но слишком часто их предел в том, что они не настоящие
  • Слой заглушенных сервисов , который вы реализуете, предлагает необходимые вам интерфейсы.
  • Реальные внешние процессы , иногда удаленные, для проверки реальных взаимодействий.

Эти стратегии работают, но они часто требуют много усилий для реализации. И самый полный, то есть тот, который использует надлежащие внешние сервисы, создает проблемы, связанные с изоляцией : представьте, что вы взаимодействуете с базой данных и выполняете операции чтения / записи, когда кто-то другой обращался к тем же ресурсам . Опять же, вы можете найти правильные рабочие процессы, которые включают создание отдельных схем и т. Д., Но, опять же, это дополнительная работа и очень часто не очень прямое действие.

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

Docker — это инструмент, который предлагает нам такую ​​возможность.

Вы можете запустить набор Docker-контейнера со всеми необходимыми службами в начале пакета тестирования и разложить его в конце. И ваша работа в Maven может быть единственным потребителем этих услуг со всей необходимой изоляцией. И вы можете легко все это Dockerfile с помощью Dockerfile s, которые, в конце концов, не намного больше, чем последовательный набор вызовов командной строки.

Давайте посмотрим, как все это включить.

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

Это официальное руководство по документации, в котором показано, как установить систему под разные дистрибутивы Linux: http://docs.docker.io/en/latest/installation/

Хотя вместо этого это очень краткое руководство, чтобы показать, как установить, если вы используете MacOSX: http://blog.javabien.net/2014/03/03/setup-docker-on-osx-the-no-brainer-way /

Когда вы будете готовы и у вас будет установлен Docker, вам нужно применить определенную конфигурацию .

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

Я использую Fedora, и файл конфигурации для изменения — /usr/lib/systemd/system/docker.service .

01
02
03
04
05
06
07
08
09
10
11
[Unit]
Description=Docker Application Container Engine
Documentation=http://docs.docker.io
After=network.target
 
[Service]
ExecStart=/usr/bin/docker -d -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
Restart=on-failure
 
[Install]
WantedBy=multi-user.target

Единственная модификация по сравнению с настройками по умолчанию, это добавление -H tcp://127.0.0.1:4243 .

Теперь, после того как я перезагрузил сценарии systemd и перезапустил службу, у меня есть демон Docker, который предоставляет мне хороший REST API, который я могу использовать с помощью curl .

1
2
3
sudo systemctl daemon-reload
sudo systemctl restart docker
curl http://127.0.0.1:4243/images/json # returns a json in output

Вы, вероятно, также хотите, чтобы эта конфигурация выдерживала будущие обновления Docker rpm. Для этого вам нужно скопировать файл, который вы только что изменили, в место, где сохранились обновления оборотов. Правильный способ добиться этого в systemd :

1
sudo cp /usr/lib/systemd/system/docker.service /etc/systemd/system

Если вы используете Ubuntu, вам нужно настроить другой файл. Посмотрите на эту страницу: http://blog.trifork.com/2013/12/24/docker-from-a-distance-the-remote-api/

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

В этот момент вы можете ожидать, что я опишу вам, как использовать плагин Maven Docker . К сожалению, это не так. Пока нет такого плагина , или, по крайней мере, я не знаю об этом. Я подумываю о том, чтобы написать один, но на данный момент я быстро решил свои проблемы с помощью плагина GMaven , небольшого количества кода на Groovy и помощи библиотеки Java.

Вот код для запуска Docker-контейнеров

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import com.jayway.restassured.RestAssured
import static com.jayway.restassured.RestAssured.*
import static com.jayway.restassured.matcher.RestAssuredMatchers.*
import com.jayway.restassured.path.json.JsonPath
import com.jayway.restassured.response.Response
 
RestAssured.baseURI = "http://127.0.0.1"
RestAssured.port = 4243
 
// here you can specify advance docker params, but the mandatory one is the name of the Image you want to use
def dockerImageConf = '{"Image":"${docker.image}"}'
def dockerImageName = JsonPath.from(dockerImageConf).get("Image")
 
log.info "Creating new Docker container from image $dockerImageName"
def response =  with().body(dockerImageConf).post("/containers/create")
 
if( 404 == response.statusCode ) {
    log.info "Docker image not found in local repo. Trying to dowload image '$dockerImageName' from remote repos"
    response = with().parameter("fromImage", dockerImageName).post("/images/create")
    def message = response.asString()
    //odd: rest api always returns 200 and doesn't return proper json. I have to grep
    if( message.contains("404") ) fail("Image $dockerImageName NOT FOUND remotely. Abort. $message}")
    log.info "Image downloaded"
 
    // retry to create the container
    response = with().body(dockerImageConf).post("/containers/create")
    if( 404 == response.statusCode ) fail("Unable to create container with conf $dockerImageConf: ${response.asString()}")
}
 
def containerId = response.jsonPath().get("Id")
 
log.info "Container created with id $containerId"
 
// set the containerId to be retrieved later during the stop phase
project.properties.setProperty("containerId", "$containerId")
 
log.info "Starting container $containerId"
with().post("/containers/$containerId/start").asString()
 
def ip = with().get("/containers/$containerId/json").path("NetworkSettings.IPAddress")
 
log.info "Container started with ip: $ip"
 
System.setProperty("MONGODB_HOSTNAME", "$ip")
System.setProperty("MONGODB_PORT", "27017")

И это тот, кто их останавливает

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
import com.jayway.restassured.RestAssured
import static com.jayway.restassured.RestAssured.*
import static com.jayway.restassured.matcher.RestAssuredMatchers.*
 
RestAssured.baseURI = "http://127.0.0.1"
RestAssured.port = 4243
 
def containerId = project.properties.getProperty('containerId')
log.info "Stopping Docker container $containerId"
with().post("/containers/$containerId/stop")
log.info "Docker container stopped"
if( true == ${docker.remove.container} ){
    with().delete("/containers/$containerId")
    log.info "Docker container deleted"
}

Будьте уверены, бегло API должен предлагать, что происходит, и встроенный комментарий должен прояснить это, но позвольте мне добавить пару комментариев. Код для запуска контейнера — это моя реализация функциональности docker run как описано в официальной документации API здесь: http://docs.docker.io/en/latest/reference/api/docker_remote_api_v1.9/#inside-docker -бег

Конкретная проблема, которую мне пришлось решить, — как передать идентификатор моего контейнера Docker из фазы Maven в другую . Я достиг функциональности благодаря линии:

1
// set the containerId to be retrieved later during the stop phase project.properties.setProperty("containerId", "$containerId")

Я также представил несколько свойств Maven, которые могут быть полезны для взаимодействия с API:

  • docker.image — имя изображения, которое вы хотите раскрутить
  • docker.remove.container — если установлено значение false, сообщает Maven, чтобы он не docker.remove.container остановленный контейнер из файловой системы (полезно проверять контейнер Docker после завершения задания)

Ex.

1
mvn verify -Ddocker.image=pantinor/fuse -Ddocker.remove.container=false

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

Это часть вывода при запуске сборки Maven с помощью команды mvn verify :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
...
[INFO] --- gmaven-plugin:1.4:execute (start-docker-images) @ gmaven-docker ---
[INFO] Creating new Docker container from image {"Image":"pantinor/centos-mongodb"}
log4j:WARN No appenders could be found for logger (org.apache.http.impl.conn.BasicClientConnectionManager).
log4j:WARN Please initialize the log4j system properly.
[INFO] Container created with id 5283d970dc16bd7d64ec08744b5ecec09b57d9a81162826e847666b8fb421dbc
[INFO] Starting container 5283d970dc16bd7d64ec08744b5ecec09b57d9a81162826e847666b8fb421dbc
[INFO] Container started with ip: 172.17.0.2
 
...
 
[INFO] --- gmaven-plugin:1.4:execute (stop-docker-images) @ gmaven-docker ---
[INFO] Stopping Docker container 5283d970dc16bd7d64ec08744b5ecec09b57d9a81162826e847666b8fb421dbc
[INFO] Docker container stopped
[INFO] Docker container deleted
 
...

Если у вас есть какие-либо вопросы или предложения, пожалуйста, дайте мне знать!

Полный Maven `pom.xml` доступен также здесь: https://raw.githubusercontent.com/paoloantinori/gmaven_docker/master/pom.xml

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
<!--?xml version="1.0"?-->
    <modelversion>4.0.0</modelversion>
    <artifactid>gmaven-docker</artifactid>
    <groupid>paolo.test</groupid>
    <version>1.0.0-SNAPSHOT</version>
    <name>Sample Maven Docker integration</name>
    <description>See companion blogpost here: </description>
    <build>
        <plugins>
            <plugin>
                <groupid>org.codehaus.gmaven</groupid>
                <artifactid>gmaven-plugin</artifactid>
                <version>1.4</version>
                <configuration>
                    <providerselection>2.0</providerselection>
                </configuration>
                <executions>
                    <execution>
                        <id>start-docker-images</id>
                        <phase>test</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <source><!--[CDATA[
import com.jayway.restassured.RestAssured
import static   com.jayway.restassured.RestAssured.*
import static   com.jayway.restassured.matcher.RestAssuredMatchers.*
  
RestAssured.baseURI = "http://127.0.0.1"
RestAssured.port = 4243
  
// here you can specify advance docker params, but the mandatory one is the name of the Image you want to use
def dockerImage = '{"Image":"pantinor/centos-mongodb"}'
  
  
log.info "Creating new Docker container from image $dockerImage"
def response =  with().body(dockerImage).post("/containers/create")
  
if( 404 == response.statusCode ) {
    log.info "[INFO] Docker Image not found. Downloading from Docker Registry"
    log.info with().parameter("fromImage", "pantinor/centos-mongodb").post("/images/create").asString()
    log.info "Image downloaded"
}
  
// retry to create the container
def containerId = with().body(dockerImage).post("/containers/create").path("Id")
  
log.info "Container created with id $containerId"
  
// set the containerId to be retrieved later during the stop phase
project.properties.setProperty("containerId", "$containerId")
  
log.info "Starting container $containerId"
with().post("/containers/$containerId/start").asString()
  
def ip = with().get("/containers/$containerId/json").path("NetworkSettings.IPAddress")
  
log.info "Container started with ip: $ip"
  
System.setProperty("MONGODB_HOSTNAME", "$ip")
System.setProperty("MONGODB_PORT", "27017")
]]-->
                        </configuration>
                    </execution>
                    <execution>
                        <id>stop-docker-images</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>execute</goal>
                        </goals>
                        <configuration>
                            <source><!--[CDATA[
import com.jayway.restassured.RestAssured
import static   com.jayway.restassured.RestAssured.*
import static   com.jayway.restassured.matcher.RestAssuredMatchers.*
  
RestAssured.baseURI = "http://127.0.0.1"
RestAssured.port = 4243
  
def containerId = project.properties.getProperty('containerId')
log.info "Stopping Docker container $containerId"
with().post("/containers/$containerId/stop")
log.info "Docker container stopped"
with().delete("/containers/$containerId")
log.info "Docker container deleted"
]]-->
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupid>com.jayway.restassured</groupid>
            <artifactid>rest-assured</artifactid>
            <version>1.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Ссылка: Интеграционное тестирование с Maven и Docker от нашего партнера JCG Паоло Антинори в блоге Someday Never Comes .