[Springboot] Redis 테스트를 위한 testContainer 사용

일단 해볼게·2023년 9월 13일
3

Springboot

목록 보기
3/26

로컬에 Redis를 띄워서 테스트 코드를 검증할 수도 있지만, 테스트 코드는 동일한 환경에서 실행되어야 한다고 생각하기 때문에 다른 방법을 찾아봤다.

  • 로컬 Redis에 이미 저장된 값이 있으면 일관적인 테스트를 보장하지 않는다.

Embedded Redis

  • 내장 레디스로서 테스트시에만 내장으로 도는 레디스 서버
  • 단점
    • 여러 오류가 많은데 오픈소스의 마지막 커밋이 2년전이라 문제에 대한 개선을 하지 않는 상태이다. (kstyrc/embedded-redisozimov/embedded-redis가 있는데, kstycs는 마지막 커밋이 4년전으로 종료되었고 그나마 ozimov는 마지막 커밋이 2년전)

TestContainer

  • docker 컨테이너를 외부 설정 없이 Java 언어만으로 구축할 수 있는 오픈소스 라이브러리
  • 장점
    • 어느 로컬에서든 격리된 환경에서 동작할 수 있다.
  • 단점
    • 로컬에서 도커를 켜 둬야 테스트가 성공한다. 테스트 시작하고 끝날 때, 컨테이너가 뜨고 꺼지기 때문이다.
    • 테스트할 때마다 컨테이너를 띄워야 하기 때문에 속도가 느리다.

속도를 감안하더라도 격리된 환경에서 테스트를 할 수 있기 때문에 TestContainer를 선택했다.

build.gradle

testImplementation "org.testcontainers:testcontainers:1.19.0"
testImplementation "org.testcontainers:junit-jupiter:1.19.0"

RedisConfig 파일 생성

main 경로에 생성한다.

@EnableCaching
@Configuration
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private int port;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    @Bean
    public RedisTemplate<String, Long> redisTemplate() { // 인자에 따라 설정 다르게 하기
        RedisTemplate<String, Long> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(
            new GenericToStringSerializer<>(Long.class)); // Long 값을 다루므로 설정 변경
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        return redisTemplate;
    }
}

RedisContainerTest 파일 생성

test 경로에 생성한다.

@Testcontainers
public class RedisTestContainerConfig {

    private static final String REDIS_IMAGE = "redis:7.0.8-alpine";
    private static final int REDIS_PORT = 6379;
    private static final GenericContainer REDIS_CONTAINER;

    static {
        REDIS_CONTAINER = new GenericContainer(REDIS_IMAGE)
            .withExposedPorts(REDIS_PORT)
            .withReuse(true);
        REDIS_CONTAINER.start();
    }

    @DynamicPropertySource
    private static void registerRedisProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.redis.host", REDIS_CONTAINER::getHost);
        registry.add("spring.data.redis.port", () -> REDIS_CONTAINER.getMappedPort(REDIS_PORT)
            .toString());
    }
}

spring.data.redis.host 를 주의하자. 블로그 보고 따라했다가 경로가 달라서 고생했다.

적용한 테스트 코드

@Nested
    @DisplayName("상품의 총 리뷰 수를 추가하는 서비스 실행 시")
    class PlusOneToTotalNumberOfReviewsByItemId {

        @Test
        @DisplayName("Redis에 값이 없으면 DB에서 가져오고, 값이 있으면 Redis에 총 리뷰 수를 +1한다.")
        void shouldPlusOneToTotalNumberOfReviewsByItemId() {
            // given
            mainCategoryRepository.save(givenMainCategory);
            subCategoryRepository.save(givenSubCategory);
            itemRepository.save(givenItem);
            userRepository.save(givenUser);
            reviewRepository.save(givenReview);

            String cacheKey = "reviewCount:Item:" + givenItem.getItemId();

            // when
            redisCacheService.plusOneToTotalNumberOfReviewsByItemId(givenItem.getItemId(),
                cacheKey);
            Long dbCount = redisCacheService.getTotalNumberOfReviewsByItemId(
                givenItem.getItemId(), cacheKey);

            Long cachedCount = redisCacheService.getTotalNumberOfReviewsByItemId(
                givenItem.getItemId(), cacheKey);

            // then
            assertEquals(dbCount, cachedCount);
        }
    }

처음에는 DB에서 데이터를 가져와 쿼리문을 확인할 수 있었고, 두번째는 Redis에서 이용하기 때문에 쿼리문 없이 데이터를 가져왔다.

주의할 점

  • Redis 컨테이너를 이용해 테스트를 진행할 시 local docker가 켜져있어야한다.
  • github actions를 이용해 CI를 구축했는데, 여기서는 통과한다.
    • Github action 서버에 docker 깔려있어서 실행이 된다고 예상된다.

적용한 프로젝트

https://github.com/prgrms-be-devcourse/BE-04-NaBMart

참고

https://devoong2.tistory.com/entry/Springboot-Redis-테스트-환경-구축하기-Embedded-Redis-TestContainer

https://giron.tistory.com/146

https://loosie.tistory.com/813#3.임베디드_Redis라이브러리_사용하기

profile
시도하고 More Do하는 백엔드 개발자입니다.

0개의 댓글