1. 테스트 환경에서의 Elasticsearch: Testcontainers 활용하기
최근 검색 서비스를 개발하면서 엘라스틱서치(Elasticsearch)를 활용하고 있었습니다. 기본적인 개발 환경에서는 문제가 없었지만, 테스트를 진행하는 과정에서 몇 가지 불편함이 있었습니다.
실제 운영 중인 Elasticsearch를 테스트에 사용해야 한다?
→ 테스트 데이터가 운영 데이터에 영향을 줄 위험이 있음.
테스트를 위한 별도의 Elasticsearch를 도커 환경에서 띄워야 한다?
→ 컨테이너 실행 여부를 확인하고 관리하는 과정이 번거로움.
CI/CD 환경에서 테스트 시 Elasticsearch 인스턴스를 어떻게 관리할 것인가?
→ CI 환경마다 Elasticsearch를 설치하는 것은 비효율적이며, 환경에 따라 테스트 결과가 달라질 수 있음.
이러한 문제를 해결하기 위해 테스트컨테이너(Testcontainers) 를 도입하게 되었습니다.
2. Testcontainers란?
Testcontainers는 통합 테스트를 지원하기 위해 개발된 오픈 소스 Java 라이브러로, 도커 컨테이너를 활용하여 외부 의존성들을 포함한 테스트 환경을 구축하고 관리하는 것을 간편하게 해 줍니다. 이를 통해 애플리케이션의 통합 테스트를 더 쉽고 빠르게 작성하고 실행할 수 있습니다.
엘라스틱서치를 사용할 때 테스트컨테이너를 활용하면 실제 운영 환경과 유사한 테스트 환경을 손쉽게 구축할 수 있으며, 테스트 종료 후 컨테이너를 자동으로 정리하여 깨끗한 상태를 유지할 수 있습니다.
3. Testcontainers를 사용하는 이유
그렇다면 테스트컨테이너를 사용하는 이유는 무엇일까요? 그 이유는 다음과 같습니다.
1. 테스트 환경을 일관되게 유지
- 테스트마다 동일한 환경을 보장하며, 운영 환경과 최대한 유사한 조건에서 테스트를 수행할 수 있어 배포 후 발생할 수 있는 문제를 최소화 할 수 있습니다.
2. 엘라스틱서치 실행이 필요한 경우에만 실행
- 로컬에 엘라스틱서치를 직접 설치하지 않아도 됩니다.
- 도커 컨테이너로 필요한 경우에만 실행하므로, 개발 환경을 깔끔하게 유지할 수 있습니다.
3. CI/CD와 연동이 용이
- CI/CD환경에서 매 테스트시 깨끗한 엘라스틱서치 컨테이너를 실행하여 테스트 신뢰도를 높일 수 있습니다.
- Elasticsearch 버전이 변경되어도 이미지만 변경하면 되므로 유지보수가 편리합니다.
4. 자원 효율성 및 유지보수 용이
- 테스트가 끝나면 컨테이너가 자동으로 종료되므로, 별도의 정리 과정이 필요하지 않습니다.
- 로컬에서 직접 Elasticsearch를 실행할 경우 발생할 수 있는 포트 충돌 문제를 방지할 수 있습니다.
지금부터 Testcontainers를 활용한 Elasticsearch 테스트 환경 구축하고, 테스트코드를 작성해 실제로 잘 작동되는지 확인해보도록 하겠습니다.
4. 설정
4-1 Testcontainers 의존성 추가
Spring Boot에서 testcontainers를 사용하기 위해 gradle 의존성을 추가해줍니다.
testImplementation "org.testcontainers:elasticsearch:1.20.6"
testImplementation "org.testcontainers:junit-jupiter:1.20.6"
4-2 테스트 컨테이너 설정
@Testcontainers
@TestConfiguration
@EnableElasticsearchRepositories(basePackageClasses = TicketElasticsearchRepository.class)
public class ElasticSearchTestContainer {
private static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.6.0";
@Container
private static final ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
.withEnv("discovery.type", "single-node")
.withEnv("xpack.security.enabled", "false")
.withEnv("xpack.security.http.ssl.enabled", "false")
.withCommand("sh", "-c", "elasticsearch-plugin install analysis-nori && elasticsearch");
static {
container.start();
}
@DynamicPropertySource
static void setElasticsearchProperties(DynamicPropertyRegistry registry) {
registry.add("spring.elasticsearch.uris", container::getHttpHostAddress);
}
@Bean
public RestClient restClient() {
return RestClient.builder(HttpHost.create(container.getHttpHostAddress())).build();
}
@Bean
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
return new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper()));
}
}
- @Testcontainers를 사용하여 테스트 컨테이너를 관리합니다.
- withEnv()를 사용하여 보안 설정을 false로 설정해줬습니다.
- 저는 Nori 형태소 분석기를 사용중이기 때문에 한글 형태소 분석기(Nori) 설치 커맨드를 추가해줬습니다.
- @DynamicPropertySource를 활용하여 스프링 속성을 동적으로 등록합니다.
- RestClient와 ElasticsearchClient를 빈으로 등록하여 스프링 컨텍스트에서 관리할 수 있도록 설정합니다.
이 때 기본으로 사용하던 elasticsearch 설정의 빈과 충돌하지 않도록 기존 Config 파일에 @Profile("!test") 설정을 추가해줬습니다.
(테스트 환경에서는 해당 빈을 사용하지 않음)
5. 테스트 코드
테스트 컨테이너를 활용해 실제 통합테스트를 진행합니다.
@SpringBootTest
@Import({ElasticSearchTestContainer.class})
@ActiveProfiles("test")
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
public class TicketElasticsearchTest {
@Autowired
private TicketSearchService ticketSearchService;
@Autowired
private TicketElasticsearchRepository elasticsearchRepository;
@Test
void 타이틀이_일치하면_티켓을_반환한다() {
TicketDocument ticket = new TicketDocument(
1L,
"모차르트",
"모차르트의 연주",
"120분",
LocalDateTime.now(),
LocalDateTime.now(),
"세종문화회관",
"뮤지컬");
elasticsearchRepository.save(ticket);
Page<TicketDocument> result = ticketSearchService.searchTicketsByFilter("모차르트", null, null, null, 0, 10);
assertThat(result).hasSize(1);
assertThat(result.getContent().get(0).getTitle()).isEqualTo("모차르트");
assertThat(result.getContent().get(0).getContent()).isEqualTo("모차르트의 연주");
}
@Test
void 타이틀이_일치하지_않으면_빈페이지를_반환한다() {
TicketDocument ticket = new TicketDocument(
1L,
"리버풀 vs 레알마드리드",
"축구 경기가",
"120분",
LocalDateTime.now(),
LocalDateTime.now(),
"수원경기장",
"스포츠");
elasticsearchRepository.save(ticket);
Page<TicketDocument> result = ticketSearchService.searchTicketsByFilter("바르셀로나", null, null, null, 0, 10);
assertThat(result).isEmpty();
}
}
도커 환경에 정상적으로 컨테이너가 띄워진 것을 확인할 수 있습니다.

테스트를 마치면 해당 컨테이너는 자동으로 종료 및 제거됩니다.

테스트가 성공적으로 수행된 것을 확인할 수 있습니다.

테스트컨테이너를 활용하면 다양한 장점이 있지만, 도커 컨테이너를 실행해야 하므로 테스트 속도가 다소 느려질 수 있다는 단점도 있습니다. 하지만 운영 환경과 유사한 환경에서 테스트할 수 있다는 점에서 충분한 가치가 있다고 생각합니다!!

참고
Testcontainers
Testcontainers is an opensource library for providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
testcontainers.com
'Backend > 프로젝트' 카테고리의 다른 글
K6 + InfluxDB + Grafana를 활용한 부하테스트 진행하기(docker) (2) | 2025.04.20 |
---|---|
Elasticsearch 활용한 검색서비스 만들기 3편(검색어 자동완성 기능 + 검색 고도화하기) (0) | 2025.03.14 |
Elasticsearch 활용한 검색서비스 만들기 2편(feat. Spring Boot) (0) | 2025.03.12 |
Elasticsearch 활용한 검색 서비스 만들기 1편 (Docker로 Elasticsearch + Kibana 구축) (0) | 2025.03.10 |
무중단 배포 적용하기 (1) | 2025.01.01 |
1. 테스트 환경에서의 Elasticsearch: Testcontainers 활용하기
최근 검색 서비스를 개발하면서 엘라스틱서치(Elasticsearch)를 활용하고 있었습니다. 기본적인 개발 환경에서는 문제가 없었지만, 테스트를 진행하는 과정에서 몇 가지 불편함이 있었습니다.
실제 운영 중인 Elasticsearch를 테스트에 사용해야 한다?
→ 테스트 데이터가 운영 데이터에 영향을 줄 위험이 있음.
테스트를 위한 별도의 Elasticsearch를 도커 환경에서 띄워야 한다?
→ 컨테이너 실행 여부를 확인하고 관리하는 과정이 번거로움.
CI/CD 환경에서 테스트 시 Elasticsearch 인스턴스를 어떻게 관리할 것인가?
→ CI 환경마다 Elasticsearch를 설치하는 것은 비효율적이며, 환경에 따라 테스트 결과가 달라질 수 있음.
이러한 문제를 해결하기 위해 테스트컨테이너(Testcontainers) 를 도입하게 되었습니다.
2. Testcontainers란?
Testcontainers는 통합 테스트를 지원하기 위해 개발된 오픈 소스 Java 라이브러로, 도커 컨테이너를 활용하여 외부 의존성들을 포함한 테스트 환경을 구축하고 관리하는 것을 간편하게 해 줍니다. 이를 통해 애플리케이션의 통합 테스트를 더 쉽고 빠르게 작성하고 실행할 수 있습니다.
엘라스틱서치를 사용할 때 테스트컨테이너를 활용하면 실제 운영 환경과 유사한 테스트 환경을 손쉽게 구축할 수 있으며, 테스트 종료 후 컨테이너를 자동으로 정리하여 깨끗한 상태를 유지할 수 있습니다.
3. Testcontainers를 사용하는 이유
그렇다면 테스트컨테이너를 사용하는 이유는 무엇일까요? 그 이유는 다음과 같습니다.
1. 테스트 환경을 일관되게 유지
- 테스트마다 동일한 환경을 보장하며, 운영 환경과 최대한 유사한 조건에서 테스트를 수행할 수 있어 배포 후 발생할 수 있는 문제를 최소화 할 수 있습니다.
2. 엘라스틱서치 실행이 필요한 경우에만 실행
- 로컬에 엘라스틱서치를 직접 설치하지 않아도 됩니다.
- 도커 컨테이너로 필요한 경우에만 실행하므로, 개발 환경을 깔끔하게 유지할 수 있습니다.
3. CI/CD와 연동이 용이
- CI/CD환경에서 매 테스트시 깨끗한 엘라스틱서치 컨테이너를 실행하여 테스트 신뢰도를 높일 수 있습니다.
- Elasticsearch 버전이 변경되어도 이미지만 변경하면 되므로 유지보수가 편리합니다.
4. 자원 효율성 및 유지보수 용이
- 테스트가 끝나면 컨테이너가 자동으로 종료되므로, 별도의 정리 과정이 필요하지 않습니다.
- 로컬에서 직접 Elasticsearch를 실행할 경우 발생할 수 있는 포트 충돌 문제를 방지할 수 있습니다.
지금부터 Testcontainers를 활용한 Elasticsearch 테스트 환경 구축하고, 테스트코드를 작성해 실제로 잘 작동되는지 확인해보도록 하겠습니다.
4. 설정
4-1 Testcontainers 의존성 추가
Spring Boot에서 testcontainers를 사용하기 위해 gradle 의존성을 추가해줍니다.
testImplementation "org.testcontainers:elasticsearch:1.20.6"
testImplementation "org.testcontainers:junit-jupiter:1.20.6"
4-2 테스트 컨테이너 설정
@Testcontainers
@TestConfiguration
@EnableElasticsearchRepositories(basePackageClasses = TicketElasticsearchRepository.class)
public class ElasticSearchTestContainer {
private static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:8.6.0";
@Container
private static final ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
.withEnv("discovery.type", "single-node")
.withEnv("xpack.security.enabled", "false")
.withEnv("xpack.security.http.ssl.enabled", "false")
.withCommand("sh", "-c", "elasticsearch-plugin install analysis-nori && elasticsearch");
static {
container.start();
}
@DynamicPropertySource
static void setElasticsearchProperties(DynamicPropertyRegistry registry) {
registry.add("spring.elasticsearch.uris", container::getHttpHostAddress);
}
@Bean
public RestClient restClient() {
return RestClient.builder(HttpHost.create(container.getHttpHostAddress())).build();
}
@Bean
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
return new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper()));
}
}
- @Testcontainers를 사용하여 테스트 컨테이너를 관리합니다.
- withEnv()를 사용하여 보안 설정을 false로 설정해줬습니다.
- 저는 Nori 형태소 분석기를 사용중이기 때문에 한글 형태소 분석기(Nori) 설치 커맨드를 추가해줬습니다.
- @DynamicPropertySource를 활용하여 스프링 속성을 동적으로 등록합니다.
- RestClient와 ElasticsearchClient를 빈으로 등록하여 스프링 컨텍스트에서 관리할 수 있도록 설정합니다.
이 때 기본으로 사용하던 elasticsearch 설정의 빈과 충돌하지 않도록 기존 Config 파일에 @Profile("!test") 설정을 추가해줬습니다.
(테스트 환경에서는 해당 빈을 사용하지 않음)
5. 테스트 코드
테스트 컨테이너를 활용해 실제 통합테스트를 진행합니다.
@SpringBootTest
@Import({ElasticSearchTestContainer.class})
@ActiveProfiles("test")
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
@SuppressWarnings("NonAsciiCharacters")
public class TicketElasticsearchTest {
@Autowired
private TicketSearchService ticketSearchService;
@Autowired
private TicketElasticsearchRepository elasticsearchRepository;
@Test
void 타이틀이_일치하면_티켓을_반환한다() {
TicketDocument ticket = new TicketDocument(
1L,
"모차르트",
"모차르트의 연주",
"120분",
LocalDateTime.now(),
LocalDateTime.now(),
"세종문화회관",
"뮤지컬");
elasticsearchRepository.save(ticket);
Page<TicketDocument> result = ticketSearchService.searchTicketsByFilter("모차르트", null, null, null, 0, 10);
assertThat(result).hasSize(1);
assertThat(result.getContent().get(0).getTitle()).isEqualTo("모차르트");
assertThat(result.getContent().get(0).getContent()).isEqualTo("모차르트의 연주");
}
@Test
void 타이틀이_일치하지_않으면_빈페이지를_반환한다() {
TicketDocument ticket = new TicketDocument(
1L,
"리버풀 vs 레알마드리드",
"축구 경기가",
"120분",
LocalDateTime.now(),
LocalDateTime.now(),
"수원경기장",
"스포츠");
elasticsearchRepository.save(ticket);
Page<TicketDocument> result = ticketSearchService.searchTicketsByFilter("바르셀로나", null, null, null, 0, 10);
assertThat(result).isEmpty();
}
}
도커 환경에 정상적으로 컨테이너가 띄워진 것을 확인할 수 있습니다.

테스트를 마치면 해당 컨테이너는 자동으로 종료 및 제거됩니다.

테스트가 성공적으로 수행된 것을 확인할 수 있습니다.

테스트컨테이너를 활용하면 다양한 장점이 있지만, 도커 컨테이너를 실행해야 하므로 테스트 속도가 다소 느려질 수 있다는 단점도 있습니다. 하지만 운영 환경과 유사한 환경에서 테스트할 수 있다는 점에서 충분한 가치가 있다고 생각합니다!!

참고
Testcontainers
Testcontainers is an opensource library for providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
testcontainers.com
'Backend > 프로젝트' 카테고리의 다른 글
K6 + InfluxDB + Grafana를 활용한 부하테스트 진행하기(docker) (2) | 2025.04.20 |
---|---|
Elasticsearch 활용한 검색서비스 만들기 3편(검색어 자동완성 기능 + 검색 고도화하기) (0) | 2025.03.14 |
Elasticsearch 활용한 검색서비스 만들기 2편(feat. Spring Boot) (0) | 2025.03.12 |
Elasticsearch 활용한 검색 서비스 만들기 1편 (Docker로 Elasticsearch + Kibana 구축) (0) | 2025.03.10 |
무중단 배포 적용하기 (1) | 2025.01.01 |