Статьи

3 способа использования Docker Containers для тестирования в Arquillian

Arquillian Cube — это расширение Arquillian, которое можно использовать для управления контейнерами Docker из Arquillian.

С этим расширением вы можете запустить Docker- контейнер (ы), выполнить тесты Arquillian и после этого закрыть контейнер (ы).

Первое, что вам нужно сделать, это добавить зависимость Arquillian Cube. Это можно сделать, используя подход Arquillian Universe:

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
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.arquillian</groupId>
            <artifactId>arquillian-universe</artifactId>
            <version>${version.arquillian_universe}</version>
            <scope>import</scope>
            <type>pom</type>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <dependency>
        <groupId>org.arquillian.universe</groupId>
        <artifactId>arquillian-junit-standalone</artifactId>
        <scope>test</scope>
        <type>pom</type>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>${version.junit}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.arquillian.universe</groupId>
      <artifactId>arquillian-cube-docker</artifactId>
      <scope>test</scope>
      <type>pom</type>
    </dependency>
</dependencies>

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

Первый подход — использование формата docker-compose . Вам нужно только определить файл docker-compose, необходимый для ваших тестов, и Arquillian Cube автоматически прочитает его, запустит все контейнеры, выполнит тесты и, наконец, после этого они остановят и удалят их.

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
version: '2'
 
services:
  pingpong:
    image: jonmorehouse/ping-pong
    ports:
      - "8080:8080"
    networks:
      app_net:
        ipv4_address: 172.16.238.10
        ipv6_address: 2001:3984:3989::10
      front:
      back:
 
networks:
  front:
    driver: bridge
  back:
    driver: bridge
  app_net:
    driver: bridge
    driver_opts:
      com.docker.network.enable_ipv6: "true"
    ipam:
      driver: default
      config:
      - subnet: 172.16.238.0/24
        gateway: 172.16.238.1
      - subnet: 2001:3984:3989::/64
        gateway: 2001:3984:3989::1
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@RunWith(Arquillian.class)
public class PingPongTest {
 
  @HostIp
  String ip;
 
  @HostPort(containerName = "pingpong", value = 8080)
  int port;
   
  @Test
  public void should_execute_a_ping_pong() {
    URL pingPongServer = new URL("http://" + ip + ":" + port);
    // ...
  }
   
}

В предыдущем примере определен файл составления Docker версии 2 (его можно сохранить в корне проекта, или в src / {main, test} / docker, или в src / {main, test} / resources, и Arquillian Cube выберет автоматически), создает определенную сеть и запускает определенный контейнер службы, выполняет данный тест. и, наконец, останавливается и удаляет сеть и контейнер. Ключевым моментом здесь является то, что это происходит автоматически, вам не нужно ничего делать вручную.

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

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
@RunWith(Arquillian.class)
public class FtpClientTest {
 
 public static final String REMOTE_FILENAME = "a.txt";
 
 @Cube
 FtpContainer ftpContainer;
 
 @Rule
 public TemporaryFolder folder = new TemporaryFolder();
 
 @Test
 public void should_upload_file_to_ftp_server() throws Exception {
 
    // Given
    final File file = folder.newFile(REMOTE_FILENAME);
    Files.write(file.toPath(), "Hello World".getBytes());
 
    // When
    FtpClient ftpClient = new FtpClient(ftpContainer.getIp(),
            ftpContainer.getBindPort(),
            ftpContainer.getUsername(), ftpContainer.getPassword());
    try {
     ftpClient.uploadFile(file, REMOTE_FILENAME, ".");
    } finally {
     ftpClient.disconnect();
    }
 
    // Then
    final boolean filePresentInContainer = ftpContainer.isFilePresentInContainer(REMOTE_FILENAME);
    assertThat(filePresentInContainer, is(true));
 
 }
 
}
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
@Cube(value = "ftp",
        portBinding =  FtpContainer.BIND_PORT +  "->21/tcp")
@Image("andrewvos/docker-proftpd")
@Environment(key = "USERNAME", value = FtpContainer.USERNAME)
@Environment(key = "PASSWORD", value = FtpContainer.PASSWORD)
public class FtpContainer {
 
 static final String USERNAME = "alex";
 static final String PASSWORD = "aixa";
 static final int BIND_PORT = 2121;
 
 @ArquillianResource
 DockerClient dockerClient;
 
 @HostIp
 String ip;
 
 public String getIp() {
  return ip;
 }
 
 public String getUsername() {
  return USERNAME;
 }
 
 public String getPassword() {
  return PASSWORD;
 }
 
 public int getBindPort() {
  return BIND_PORT;
 }
 
 public boolean isFilePresentInContainer(String filename) {
  try(
   final InputStream file = dockerClient.copyArchiveFromContainerCmd("ftp",
           "/ftp/" + filename).exec()) {
   return file != null;
  } catch (Exception e) {
   return false;
  }
  
 }
}

В этом случае вы используете аннотации, чтобы определить, как должен выглядеть контейнер. Также, поскольку вы используете объекты Java, вы можете добавить методы, которые инкапсулируют операции с самим контейнером, как в этом объекте, где операция проверки, был ли загружен файл, была добавлена ​​в объект контейнера.

Наконец, в вашем тесте вам нужно только аннотировать его аннотацией @Cube .

Обратите внимание, что вы даже можете создать определение контейнера программно:

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
@Cube(value = "pingpong", portBinding = "5000->8080/tcp")
public class PingPongContainer {
 
  @HostIp
  String dockerHost;
 
  @HostPort(8080)
  private int port;
 
  @CubeDockerFile
  public static Archive<?> createContainer() {
    String dockerDescriptor = Descriptors.create(DockerDescriptor.class)
              .from("jonmorehouse/ping-pong")
              .expose(8080)
              .exportAsString();
    return ShrinkWrap.create(GenericArchive.class)
              .add(new StringAsset(dockerDescriptor), "Dockerfile");
  }
 
  public int getConnectionPort() {
    return port;
  }
 
  public String getDockerHost() {
      return this.dockerHost;
  }
}

В этом случае файл Dockerfile создается программным способом в объекте контейнера и используется для сборки и запуска контейнера.
Третий способ — использование объекта контейнера DSL. Этот подход позволяет избежать создания класса объекта-контейнера и использования аннотаций для его определения. Он может быть создан с использованием DSL, предоставленного для этой цели:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@RunWith(Arquillian.class)
public class PingPongTest {
 
  @DockerContainer
  Container pingpong = Container.withContainerName("pingpong")
                                .fromImage("jonmorehouse/ping-pong")
                                .withPortBinding(8080)
                                .build();
 
  @Test
  public void should_return_ok_as_pong() throws IOException {
    String response = ping(pingpong.getIpAddress(), pingpong.getBindPort(8080));
    assertThat(response).containsSequence("OK");
  }
}

В этом случае подход очень похож на предыдущий, но вы используете DSL для определения контейнера.

У вас есть три способа, первый — стандартный, следующий за соглашениями о создании docker, другие могут быть использованы для определения повторно используемых частей для ваших тестов.

Вы можете прочитать больше об Arquillian Cube на http://arquillian.org/arquillian-cube/

Мы продолжаем учиться,
Alex

И ты думал, что этот дурак никогда не сможет победить? Хорошо, посмотри на меня, я возвращаюсь снова, я почувствовал вкус любви простым способом, И если тебе нужно знать, пока я все еще стою, ты просто исчезнешь ( Я все еще стою — Элтон Джон)

Музыка: https://www.youtube.com/watch?v=ZHwVBirqD2s