Статьи

Тестирование приложений Spring Data + Spring Boot с помощью Arquillian (часть 1)

Миссия Spring Data — предоставить знакомую и согласованную модель программирования на основе Spring для доступа к данным, сохраняя при этом особые черты основного хранилища данных. Он обеспечивает интеграцию с несколькими базовыми технологиями, такими как JPA, Rest, MongoDB, Neo4J или Redis.

Так что, если вы используете Spring (Boot), то Spring Data — правильный выбор для работы со слоем постоянства.

В следующем примере вы можете увидеть, как просто использовать Spring Boot и Spring Data Redis .

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
@Controller
@EnableAutoConfiguration
public class PingPongController {
 
    @Autowired
    StringRedisTemplate redisTemplate;
 
    @RequestMapping("/{ping}")
    @ResponseBody
    List<String> getPong(@PathVariable("ping") String ping) {
 
        final ListOperations<String, String> stringStringListOperations = redisTemplate.opsForList();
        final Long size = stringStringListOperations.size(ping);
        return stringStringListOperations.range(ping, 0, size);
    }
 
    @RequestMapping(value="/{ping}", method = RequestMethod.POST)
    ResponseEntity<?> addPong(@PathVariable("ping") String ping, @RequestBody String pong) {
 
        final ListOperations<String, String> stringStringListOperations = redisTemplate.opsForList();
        stringStringListOperations.leftPushAll(ping, pong);
 
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .buildAndExpand(ping).toUri();
 
        return ResponseEntity.created(location).build();
    }
 
 
    public static void main(String[] args) {
        SpringApplication.run(PingPongController.class, args);
    }
 
}
1
2
3
4
5
6
7
8
9
@Configuration
public class RedisConfiguration {
 
    @Bean
    StringRedisTemplate template(final RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }
 
}

Важно отметить, что по умолчанию Spring Data Redis настроен для подключения к localhost и порту 6379 , но вы можете переопределить эти значения, установив системные свойства ( spring.redis.host и spring.redis.port ) или переменные среды ( SPRING_REDIS_HOST и SPRING_REDIS_PORT ).

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

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

Чтобы избежать этой проблемы, одним из возможных решений является использование Docker и контейнеров. Таким образом, вместо того, чтобы устанавливать каждую базу данных в системе, вы зависите только от Docker . Затем тест просто запускает контейнер репозитория, в нашем случае Redis, выполняет тест (ы) и, наконец, останавливает контейнер.

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

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

По умолчанию сервер Docker использует сокеты UNIX для связи с клиентом Docker . Arquillian Cube попытается определить операционную систему, в которой он работает, и либо установить docker-java для использования сокета UNIX в Linux или Boot2Docker / Docker-Machine в Windows / Mac в качестве URI по умолчанию , поэтому ваш тест переносим для нескольких установок Docker и вам не нужно беспокоиться о его настройке, Arquillian Cube адаптируется к тому, что вы установили.
Arquillian Cube предлагает три различных способа определения контейнера (ов).

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

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

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

В этом случае правило JUnit используется, чтобы определить, какое изображение следует использовать в тесте ( redis: 3.2.6 ), и добавить в качестве связующего порта порт Redis ( 6379 ).

Полный тест выглядит так:

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
@RunWith(SpringRunner.class)
@SpringBootTest(classes = PingPongController.class, webEnvironment = RANDOM_PORT)
@ContextConfiguration(initializers = PingPongSpringBootTest.Initializer.class)
public class PingPongSpringBootTest {
 
    @ClassRule
    public static ContainerDslRule redis = new ContainerDslRule("redis:3.2.6")
                                                .withPortBinding(6379);
 
    @Autowired
    TestRestTemplate restTemplate;
 
    @Test
    public void should_get_pongs() {
 
        // given
 
        restTemplate.postForObject("/ping", "pong", String.class);
        restTemplate.postForObject("/ping", "pung", String.class);
 
        // when
 
        final List<String> pings = restTemplate.getForObject("/ping", List.class);
 
        // then
 
        assertThat(pings)
            .hasSize(2)
            .containsExactlyInAnyOrder("pong", "pung");
    }
 
    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            EnvironmentTestUtils.addEnvironment("testcontainers", configurableApplicationContext.getEnvironment(),
                "spring.redis.host=" + redis.getIpAddress(),
                "spring.redis.port=" + redis.getBindPort(6379)
            );
        }
    }
 
}

Обратите внимание, что это простой тест Spring Boot с использованием их битов и бобов, но в тесте используется правило JQnit Arquillian Cube для запуска и остановки образа Redis.

Последнее, что следует отметить, это то, что test содержит реализацию ApplicationContextInitializer, поэтому мы можем сконфигурировать среду с данными Docker (хост и порт привязки контейнера Redis), чтобы Spring Data Redis мог подключиться к правильному экземпляру.

Последний, но не менее важный файл build.gradle определяет необходимые зависимости, которые выглядят так:

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
buildscript {
    repositories {
        jcenter()
        mavenCentral()
    }
 
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}
 
plugins {
    id "io.spring.dependency-management" version "1.0.2.RELEASE"
}
 
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
 
repositories {
    jcenter()
}
 
project.version = '1.0.0'
 
dependencyManagement {
    imports {
        mavenBom 'org.jboss.arquillian:arquillian-bom:1.1.13.Final'
    }
}
 
dependencies {
 
    compile "org.springframework.boot:spring-boot-starter-web:1.5.2.RELEASE"
    compile 'org.springframework.boot:spring-boot-starter-data-redis:1.5.2.RELEASE'
    testCompile 'org.springframework.boot:spring-boot-starter-test:1.5.2.RELEASE'
    testCompile 'junit:junit:4.12'
    testCompile 'org.arquillian.cube:arquillian-cube-docker-junit-rule:1.2.0'
    testCompile 'org.assertj:assertj-core:3.6.2'
}
Вы можете прочитать больше об Arquillian Cube на http://arquillian.org/arquillian-cube/