Статьи

Запуск параллельных тестов в Docker

Иногда, когда вы запускаете свои тесты в своей среде CI, вы хотите запускать тесты параллельно. Этот параллелизм программируется в инструменте сборки, таком как Maven или Gradle, или с помощью плагина Jenkins .

Если вы используете Docker в качестве инструмента тестирования для предоставления внешних зависимостей приложению (например, баз данных, почтовых серверов, FTP-серверов,…), вы можете столкнуться с большой проблемой, и это, вероятно,

Docker Hos t используется один и при параллельном запуске тестов все они попытаются запустить контейнер с тем же именем. Поэтому, когда вы запускаете второй тест (параллельно), вы получите ошибку, связанную с именем контейнера конфликта, из-за попытки запустить на одном и том же Docker Host два контейнера с одинаковым именем или имеющие одинаковый порт привязки в двух контейнерах.

Итак, достигнув этой точки, вы можете сделать две вещи:

  • Вы можете иметь один Docker Host для каждого параллельного теста.
  • Вы можете повторно использовать тот же Docker Host и использовать Arquillian Cube Star Operator .

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

Чтобы использовать Arquillian Cube, вам нужен демон Docker , работающий на компьютере (он может быть локальным или нет), но, вероятно, он будет локальным.

Arquillian Cube предлагает три различных способа определения контейнера (ов):

  • Определение файла docker-compose .
  • Определение объекта-контейнера.
  • Использование контейнерного объекта DSL.

В этом примере я собираюсь показать вам, как использовать docker-compose и Container Object DSL .

Звездный оператор позволяет указать 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
plugins {
    id "io.spring.dependency-management" version "1.0.2.RELEASE"
}
 
 
apply plugin: 'java'
 
repositories {
    mavenCentral()
    jcenter()
}
 
dependencyManagement {
    imports {
        mavenBom 'org.jboss.arquillian:arquillian-bom:1.1.13.Final'
    }
}
 
dependencies {
 
    testCompile 'junit:junit:4.12'
    testCompile 'org.jboss.arquillian.junit:arquillian-junit-standalone'
    testCompile 'org.arquillian.cube:arquillian-cube-docker:1.3.2'
}
 
test {
    maxParallelForks = 2
    testLogging.showStandardStreams = true
}
1
2
3
4
5
6
#src/test/docker/docker-compose.yml
 
redis*:
  image: redis:3.0.7
  ports:
    - "6379"
01
02
03
04
05
06
07
08
09
10
11
12
13
14
@RunWith(Arquillian.class)
public class TestOne {
 
    @HostPort(containerName = "redis*", value = 6379)
    private int portBinding;
 
 
    @Test
    public void should_print_port_binding() throws InterruptedException {
        System.out.println(TestOne.class.getCanonicalName() + " - " + portBinding);
        TimeUnit.SECONDS.sleep(4);
    }
 
}

Вы можете увидеть в файле docker-compose.yml важное изменение в типичном файле docker-compose , и оно заключается в том, что имя заканчивается оператором star (*) [ redis * ]. Вот как вы указываете Arquillian Cube, что это имя должно генерироваться динамически для каждого выполнения.
Тогда есть три теста (здесь только показали первый), что все они выглядят одинаково. В основном он печатает на консоли порт привязки для подключения к серверу.
Наконец, есть файл build.gradle , который выполняет два теста параллельно. Поэтому, если вы запустите тесты в Gradle ( ./gradlew test ), вы увидите, что два теста выполняются одновременно, и когда он завершит один из них, будет выполнен оставшийся тест. Затем, если вы проверите вывод, вы увидите следующий вывод:

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
46
47
48
49
50
51
52
53
54
org.superbiz.parallel.runner.TestOne STANDARD_OUT
    CubeDockerConfiguration:
      serverUri = tcp://192.168.99.100:2376
      machineName = dev
      certPath = /Users/alex/.docker/machine/machines/dev
      tlsVerify = true
      dockerServerIp = 192.168.99.100
      definitionFormat = COMPOSE
      clean = false
      removeVolumes = true
      dockerContainers = containers:
      redis_9efae4a8-fcb5-4f9e-9b1d-ab591a5c4d5a:
        alwaysPull: false
        image: redis:3.0.7
        killContainer: false
        manual: false
        portBindings: !!set {56697->6379/tcp: null}
        readonlyRootfs: false
        removeVolumes: true
    networks: {}
 
 
 
org.superbiz.parallel.runner.TestThree STANDARD_OUT
    CubeDockerConfiguration:
      serverUri = tcp://192.168.99.100:2376
      machineName = dev
      certPath = /Users/alex/.docker/machine/machines/dev
      tlsVerify = true
      dockerServerIp = 192.168.99.100
      definitionFormat = COMPOSE
      clean = false
      removeVolumes = true
      dockerContainers = containers:
      redis_88ff4b81-80cc-43b3-8bbe-8638dd731d8e:
        alwaysPull: false
        image: redis:3.0.7
        killContainer: false
        manual: false
        portBindings: !!set {56261->6379/tcp: null}
        readonlyRootfs: false
        removeVolumes: true
    networks: {}
     
    //......
     
org.superbiz.parallel.runner.TestThree > should_print_port_binding STANDARD_OUT
    org.superbiz.parallel.runner.TestOne - 56261
 
org.superbiz.parallel.runner.TestOne > should_print_port_binding STANDARD_OUT
    org.superbiz.parallel.runner.TestOne - 56697
 
org.superbiz.parallel.runner.TestTwo > should_print_port_binding STANDARD_OUT
    org.superbiz.parallel.runner.TestOne - 56697

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

Но если вы не хотите использовать подход docker-compose , вы также можете определить контейнер программно, используя объект-контейнер DSL, который также поддерживает оператор звезды . В этом случае это выглядит так:

1
2
3
4
@ClassRule
public static ContainerDslRule redisStar =
  new ContainerDslRule("redis:3.2.6", "redis*")
  .withPortBinding(6379);

Подход тот же, но с использованием объектов-контейнеров (вам нужен Arquillian Cube 1.4.0 для запуска его с объектами-контейнерами).
Обратите внимание, что благодаря этой функции вы можете запускать тесты с любой степенью параллельного выполнения, поскольку Arquillian Cube решает проблемы с именами или привязкой портов. Обратите внимание, что в случае связи между контейнерами вам все равно нужно использовать оператор «звезда», и он будет решен во время выполнения.

Чтобы узнать больше о звездном операторе, просто проверьте http://arquillian.org/arquillian-cube/#_parallel_execution

Исходный код: https://github.com/lordofthejars/parallel-docker

Ссылка: Проведение параллельных тестов в Docker от нашего партнера JCG Алекса Сото в блоге One Jar To Rule All .