Elasticsearch를 직접 체험하려면 당사의 샘플 노트북을 살펴보거나, 무료 클라우드 체험판을 시작하거나, 지금 바로 로컬 기기에서 Elastic을 사용해 보세요.
이 포스팅에서는 외부 시스템 종속성으로 Elasticsearch를 사용하여 소프트웨어를 테스트하는 두 가지 방법을 소개하고 설명합니다. 모의 테스트와 통합 테스트를 사용하여 테스트하고, 두 테스트 간의 실질적인 차이점을 보여주고, 각 스타일에 대한 몇 가지 힌트를 제공합니다.
시스템 신뢰도를 위한 좋은 테스트
좋은 테스트는 IT 시스템을 만들고 유지 관리하는 과정에 참여하는 모든 사람의 자신감을 높여주는 테스트입니다. 테스트는 멋지거나 빠르거나 인위적으로 코드 커버리지를 늘리기 위한 것이 아닙니다. 테스트는 이를 보장하는 데 중요한 역할을 합니다:
- 우리가 제공하고자 하는 것은 프로덕션에서 작동할 것입니다.
- 시스템은 요구 사항과 계약을 충족합니다.
- 앞으로는 퇴행이 없을 것입니다.
- 개발자(및 기타 관련 팀원)는 자신이 만든 콘텐츠가 제대로 작동할 것이라고 확신합니다.
물론 그렇다고 해서 테스트가 멋지거나 빠르거나 코드 커버리지를 늘릴 수 없다는 의미는 아닙니다. 테스트 스위트를 더 빨리 실행할수록 좋습니다. 다만 테스트 스위트의 전체 기간을 단축하기 위해 자동화된 테스트가 제공하는 신뢰성, 유지보수성, 자신감을 희생해서는 안 된다는 것입니다.
좋은 자동화된 테스트는 다양한 팀원들의 자신감을 높여줍니다:
- 개발자: 개발자는 작업 중인 코드가 컴퓨터를 떠나기 전에도 자신이 하고 있는 일이 제대로 작동하는지 확인할 수 있습니다.
- 품질 보증 팀: 수동으로 테스트할 일이 줄어듭니다.
- 시스템 운영자 및 SRE: 시스템 배포 및 유지 관리가 더 쉬워지므로 더 편안합니다.
마지막으로 중요한 것은 시스템의 아키텍처입니다. 시스템이 체계적이고 유지 관리가 쉬우며 아키텍처가 깔끔하고 목적에 부합할 때 저희는 이를 좋아합니다. 그러나 때때로 우리는 "이 방법이 더 테스트하기 쉽다는 핑계로 너무 많은 것을 희생하는 아키텍처를 볼 수 있습니다". 시스템이 그 존재를 정당화하는 필요를 충족시키는 대신 주로 테스트 가능하도록 작성된 경우, 꼬리가 개를 흔드는 상황을 보게 됩니다.
두 가지 종류의 테스트: 모의 & 종속성
테스트는 여러 가지 방법으로 볼 수 있으며, 따라서 분류할 수도 있습니다. 이 글에서는 테스트 분할의 한 가지 측면, 즉 모의(또는 스텁, 가짜 등)를 사용하는 것과 실제 종속성을 사용하는 것에 대해서만 집중적으로 살펴보겠습니다. 저희의 경우 종속성은 Elasticsearch입니다.
모의 테스트를 사용하는 테스트는 외부 종속성을 시작할 필요가 없고 모든 것이 메모리 내에서만 이루어지기 때문에 매우 빠릅니다. 자동화된 테스트에서 모킹은 실제 종속성을 사용하지 않고 프로그램의 일부를 테스트하기 위해 실제 객체 대신 가짜 객체를 사용하는 것을 말합니다. 이것이 바로 이러한 기능이 필요한 이유이며, 예를 들어 빠른 탐지 네트워크 테스트에서 빛을 발하는 이유입니다. 입력 유효성 검사. 예를 들어 요청에 음수가 허용되지 않는지 확인하기 위해서만 데이터베이스를 시작하고 호출할 필요가 없습니다.
하지만 모의고사를 도입하는 데에는 몇 가지 의미가 있습니다:
- 모든 것을, 모든 시간을 쉽게 모킹할 수 있는 것은 아니므로 모킹은 시스템 아키텍처에 영향을 미칩니다(때로는 훌륭하지만 때로는 그렇지 않을 수도 있습니다).
- 모의 테스트에서 실행되는 테스트는 빠를 수 있지만, 모방하는 시스템을 깊이 반영한 모의 테스트는 일반적으로 무료로 제공되지 않기 때문에 이러한 테스트를 개발하는 데 상당한 시간이 걸릴 수 있습니다. 시스템이 어떻게 작동하는지 아는 사람이 적절한 방식으로 모의고사를 작성해야 하며, 이러한 지식은 실무 경험, 문서 공부 등을 통해 얻을 수 있습니다.
- 모의고사를 유지 관리해야 합니다. 시스템이 외부 종속성에 의존하고 있고 이 종속성을 업그레이드해야 하는 경우, 누군가는 종속성을 모방한 모의 프로젝트도 모든 변경 사항(문서화 및 미문서화)이 업데이트되도록 해야 합니다(시스템에도 영향을 미칠 수 있음). 종속성을 업그레이드하고 싶지만 (모의 테스트만 사용하는) 테스트 스위트로는 테스트한 모든 케이스가 작동한다는 확신을 줄 수 없을 때 특히 문제가 됩니다.
- 모의 테스트가 아닌 시스템 개발과 테스트에 집중할 수 있도록 절제된 노력이 필요합니다.
이러한 이유로 많은 사람들이 모의(또는 스텁 등)를 사용하지 않고 실제 종속성에만 의존하는 정반대 방향을 옹호합니다. 이 접근 방식은 데모 또는 시스템의 규모가 작고 커버리지가 큰 테스트 케이스가 몇 개만 있는 경우에 매우 효과적입니다. 이러한 테스트는 통합 테스트(대략적으로 말하면 일부 실제 종속성에 대해 시스템의 일부를 확인하는 것) 또는 엔드투엔드 테스트(모든 실제 종속성을 동시에 사용하고 시스템이 사용 가능하고 성공적인 것으로 정의하는 사용자 워크플로우를 재생하면서 모든 끝에서 시스템의 동작을 확인하는 것)일 수 있습니다. 이 접근 방식을 사용하면 종속성에 대한 가정과 이를 작업 중인 시스템과 통합하는 방법을 (종종 의도치 않게) 검증할 수 있다는 분명한 이점이 있습니다.
그러나 테스트에서 실제 종속성만 사용하는 경우에는 다음과 같은 측면을 고려해야 합니다:
- 일부 테스트 시나리오에서는 실제 종속성이 필요하지 않습니다(예: 요청의 정적 불변성을 확인하는 경우).
- 피드백을 기다리는 데 너무 많은 시간이 걸리기 때문에 이러한 테스트는 일반적으로 개발자의 컴퓨터에서 전체 제품군으로 실행되지 않습니다.
- CI 머신에서 더 많은 리소스가 필요하며, 시간 낭비를 방지하기 위해 & 리소스를 조정하는 데 더 많은 시간이 소요될 수 있습니다.
- 테스트 데이터로 종속성을 초기화하는 것은 간단하지 않을 수 있습니다.
- 실제 종속성이 있는 테스트는 주요 리팩토링, 마이그레이션 또는 종속성 업그레이드 전에 코드를 묶는 데 유용합니다.
- 테스트 대상 시스템의 내부에 대해 자세히 설명하지 않고 결과만 처리하는 등 불투명한 테스트일 가능성이 높습니다.
최적의 지점: 두 가지 테스트 모두 사용
한 가지 유형의 테스트만 사용하여 시스템을 테스트하는 대신, 두 가지 유형 모두에 의존하여 두 가지 유형 모두의 사용법을 개선할 수 있습니다.
- 모의 기반 테스트는 훨씬 빠르므로 먼저 실행하고, 모두 성공하면 그 후에야 느린 종속성 테스트를 실행하세요.
- 외부 종속성이 실제로 필요하지 않은 시나리오에서는 모의 작업을 선택하고, 모의 작업에만 너무 많은 시간이 소요되어 코드를 대규모로 변경해야 하는 경우에는 외부 종속성에 의존하세요.
- 두 가지 접근 방식을 모두 사용하여 코드를 테스트하는 것이 타당하다면 잘못된 것은 없습니다.
SystemUnderTest의 예
다음 섹션에서는 여기에서 찾을 수 있는 예제를 사용하겠습니다. Java 21로 작성된 작은 데모 애플리케이션으로, 빌드 도구로 Maven을 사용하고, Elasticsearch 클라이언트에 의존하며, Elasticsearch의 최신 추가 기능인 ES|QL (Elastic의 새로운 절차적 쿼리 언어)을 사용하고 있습니다. Java가 프로그래밍 언어가 아니더라도 아래에서 설명할 개념을 이해하고 이를 스택에 적용하는 데 문제가 없을 것입니다. 실제 코드 예제를 사용하면 특정 사항을 더 쉽게 설명할 수 있습니다.
BookSearcher 은 데이터를 검색하고 분석하는 데 도움이 되며, 우리의 경우에는 책이 됩니다( 이전 게시물 중 하나에서 설명한 것처럼).
- 예를 들어, 코드가 이전 버전과 호환되는지 확실하지 않고 이전 버전과 호환되지 않는지 확실하지 않기 때문에 유일한 종속성(
isCompatibleWithBackend()참조)으로서 정확히 버전8.15.x의 Elasticsearch가 필요합니다. 프로덕션 환경의 Elasticsearch를 최신 버전으로 업그레이드하기 전에 먼저 테스트 대상 시스템의 동작이 동일하게 유지되는지 확인하기 위해 테스트에서 범프를 실행합니다. - 이를 사용하여 특정 연도에 출판된 도서의 수를 검색할 수 있습니다(
numberOfBooksPublishedInYear참조). - 데이터 집합을 분석하여 특정 연도 사이에 가장 많이 게시된 20명의 저자를 찾아야 할 때도 이 기능을 사용할 수 있습니다(
mostPublishedAuthorsInYears참조).
모의 테스트를 통해 시작하기
테스트에 사용되는 모형을 만들기 위해 Java 에코시스템에서 매우 인기 있는 모킹 라이브러리인 Mockito를 사용하겠습니다.
각 테스트 전에 모의고사를 초기화하기 위해 다음과 같이 시작할 수 있습니다:
앞서 말했듯이 모의 테스트를 통해 모든 것을 쉽게 테스트할 수 있는 것은 아닙니다. 하지만 우리가 할 수 있고 해야만 하는 일들도 있습니다. 지금은 8.15.x 버전만 지원되는지 확인해 보겠습니다(향후 시스템이 향후 버전과 호환되는지 확인되면 범위를 확장할 수 있습니다):
BookSearcher 이 아직 8.16.x 과 호환될지 확실하지 않기 때문에 다른 부 버전을 반환하여 비슷한 방식으로 이 작동하지 않는다는 것을 확인할 수 있습니다:
이제 실제 Elasticsearch를 대상으로 테스트할 때 비슷한 결과를 얻을 수 있는 방법을 살펴보겠습니다. 이를 위해 테스트컨테이너의 Elasticsearch 모듈을 사용하려고 하는데, 이 모듈의 요구 사항은 단 한 가지, 즉 Docker에 액세스할 수 있어야 한다는 것입니다. 어떤 각도에서 보면 테스트 컨테이너는 단순히 Docker 컨테이너를 작동하는 방법이지만, Docker 데스크톱(또는 이와 유사한), CLI 또는 스크립트에서 이를 수행하는 대신 사용자가 알고 있는 프로그래밍 언어로 요구 사항을 표현할 수 있습니다. 이를 통해 이미지 가져오기, 컨테이너 시작, 테스트 후 가비지 수집, 파일 앞뒤로 복사, 명령 실행, 로그 검사 등을 테스트 코드에서 직접 수행할 수 있습니다.
스텁은 다음과 같이 보일 수 있습니다:
이 예에서는 @Testcontainers 및 @Container 과의 Testcontainers의 JUnit 통합을 사용하므로, 테스트 전에 Elasticsearch를 시작하고 테스트 후에 중지하는 것에 대해 걱정할 필요가 없습니다. 각 테스트 전에 클라이언트를 생성하고 각 테스트 후에 클라이언트를 닫기만 하면 됩니다(더 큰 테스트 세트에 영향을 줄 수 있는 리소스 누수를 방지하기 위해).
정적이 아닌 필드에 @Container 주석을 달면 각 테스트마다 새 컨테이너가 시작되므로 오래된 데이터나 컨테이너의 상태 재설정에 대해 걱정할 필요가 없습니다. 그러나 많은 테스트에서 이 접근 방식은 성능이 좋지 않을 수 있으므로 다음 게시물 중 하나에서 다른 접근 방식과 비교해보겠습니다.
참고:
docker.elastic.co(Elastic의 공식 Docker 이미지 리포지토리)를 사용하면 Docker 허브의 한계를 초과하지 않아도 됩니다.
또한 테스트 환경과 프로덕션 환경에서 동일한 버전의 종속성을 사용하여 호환성을 최대한 보장하는 것이 좋습니다. 또한 Elasticsearch 이미지에는latest태그가 없으므로 버전을 정확하게 선택하는 것이 좋습니다.
테스트에서 Elasticsearch에 연결
Elasticsearch Java 클라이언트는 보안 및 SSL/TLS가 활성화된 상태에서도 테스트 컨테이너에서 실행 중인 Elasticsearch에 연결할 수 있습니다(버전 8.x의 기본값이므로 컨테이너 선언에 보안과 관련된 내용을 지정할 필요가 없었습니다). 프로덕션에서 사용 중인 Elasticsearch에도 TLS와 일부 보안이 활성화되어 있다고 가정하면, 통합 테스트 설정은 가능한 한 프로덕션 시나리오에 가깝게 설정하는 것이 좋으므로 테스트에서 이를 비활성화하지 않는 것이 좋습니다.
컨테이너가 필드 또는 변수에 할당되었다고 가정하여 연결에 필요한 데이터를 얻는 방법 elasticsearch:
elasticsearch.getHost()는 컨테이너가 실행 중인 호스트를 알려줍니다(대부분의 경우"localhost"이지만 설정에 따라 다른 이름일 수도 있으므로 호스트는 항상 동적으로 가져와야 합니다.) 하드코딩하지 마세요.elasticsearch.getMappedPort(9200)는 컨테이너 내부에서 실행 중인 Elasticsearch에 연결하기 위해 사용해야 하는 호스트 포트를 제공합니다(컨테이너를 시작할 때마다 외부 포트가 달라지므로 이 역시 동적 호출이어야 합니다).- 덮어쓰지 않는 한 기본 사용자 아이디와 비밀번호는 각각
"elastic"및"changeme"입니다. - 컨테이너 설정 중에 SSL/TLS 인증서가 지정되지 않았고 보안 연결이 비활성화되지 않은 경우(버전 8.x의 기본 동작), 자체 서명된 인증서가 생성됩니다. 신뢰하려면(예 처럼) 인증서는
elasticsearch.caCertAsBytes()()를 사용하여 얻을 수Optional<byte[]>있으며, 또 다른 편리한 방법은 를SSLContext사용하여 를 얻는createSslContextFromCa()것입니다.
전체 결과는 다음과 같습니다:
ElasticsearchClient 인스턴스 생성의 또 다른 예는 데모 프로젝트에서 찾을 수 있습니다.
참고:
프로덕션 환경에서 클라이언트를 생성하려면 문서를 참조하세요.
첫 번째 통합 테스트
첫 번째 테스트인 Elasticsearch 버전 8.15.x를 사용하여 BookSearcher 을 생성할 수 있는지 확인하는 테스트는 다음과 같습니다:
보시다시피 다른 설정은 따로 할 필요가 없습니다. 우리가 해야 할 일은 테스트컨테이너에서 시작한 실제 Elasticsearch 인스턴스에 연결된 클라이언트를 BookSearcher 에 제공하기만 하면 됩니다.
통합 테스트는 내부에 대한 관심이 적습니다.
열 인덱스를 사용하여 결과 집합에서 데이터를 추출하는 것을 중단하고 열 이름에 의존해야 한다고 가정해 보겠습니다. 따라서 방법에서 isCompatibleWithBackend 대신
우리가 가질 것입니다:
두 테스트를 다시 실행하면 실제 Elasticsearch와의 통합 테스트가 여전히 문제 없이 통과된다는 것을 알 수 있습니다. 그러나 모의 호출을 사용한 테스트는 rs.getInt(String) 이 아닌 rs.getInt(int) 과 같은 호출을 모의했기 때문에 작동이 중지되었습니다. 이제 테스트 스위트에 있는 다른 사용 사례에 따라 두 가지를 대신 모의하거나 둘 다 모의해야 통과할 수 있습니다.
통합 테스트는 파리를 잡는 대포가 될 수 있습니다.
통합 테스트는 외부 종속성이 필요하지 않더라도 시스템의 동작을 검증할 수 있습니다. 그러나 이러한 방식으로 사용하면 일반적으로 실행 시간과 리소스가 낭비됩니다. 방법을 살펴보자 mostPublishedAuthorsInYears(int minYear, int maxYear). 처음 두 줄은 다음과 같습니다:
첫 번째 문은 조건을 확인하는 것으로, 어떤 식으로든 Elasticsearch(또는 다른 외부 종속성)에 의존하지 않습니다. 따라서 minYear 이 maxYear 보다 크면 예외가 발생한다는 것을 확인하기 위해 컨테이너를 시작할 필요가 없습니다.
빠르고 리소스를 많이 사용하지 않는 간단한 모의 테스트만으로도 이를 충분히 확인할 수 있습니다. 모의 테스트를 설정한 후에는 간단히 진행할 수 있습니다:
이 테스트 케이스에서는 이 종속성에 대해 의미 있는 호출을 할 가능성이 없기 때문에 모킹 대신 종속성을 시작하는 것은 낭비입니다.
그러나 String query = ... 로 시작하는 동작을 확인하려면 쿼리가 올바르게 작성되었는지, 클라이언트 라이브러리가 적절한 요청과 응답을 보낼 수 있는지, 구문 변경이 없으므로 통합 테스트 등을 사용하여 예상대로 결과가 나오는지 등을 확인하는 것이 훨씬 쉽습니다:
이렇게 하면 데이터 형식이 변경되지 않았고 쿼리가 여전히 유효하며 모든 미들웨어(클라이언트, 드라이버, 보안 등)가 계속 작동할 것이므로 데이터를 Elasticsearch에 공급할 때(이번 버전 또는 향후 마이그레이션하기로 선택한 버전에서) 쿼리가 예상했던 것과 정확히 일치하는 결과를 제공할 것이라는 확신을 가질 수 있게 됩니다. 모형을 최신 상태로 유지하는 것에 대해 걱정할 필요가 없습니다. 8.15 이 바뀌게 될 것입니다:
다음과 같이 결정한 경우에도 마찬가지입니다. ES|QL 대신 기존의 QueryDSL을 사용하면 언어에 관계없이 쿼리에서 받는 결과는 여전히 동일해야 합니다.
필요한 경우 두 가지 접근 방식 모두 사용
mostPublishedAuthorsInYears 메서드의 사례는 두 가지 메서드를 모두 사용하여 단일 메서드를 테스트할 수 있음을 보여줍니다. 그리고 어쩌면 그렇게 해야 할지도 모릅니다.
- 모의 버전만 사용한다는 것은 시스템을 업그레이드할 때 모의 버전을 유지해야 하고 자신감이 없다는 뜻입니다.
- 통합 테스트만 사용하면 전혀 필요하지도 않은 리소스를 낭비하는 셈이 됩니다.
요약해 보겠습니다.
- 모의 테스트와 Elasticsearch와의 통합 테스트를 모두 사용할 수 있습니다.
- 모의 테스트를 빠른 탐지망으로 사용하고 성공적으로 통과한 경우에만 종속성 테스트를 시작합니다(예:
./mvnw test '-Dtest=!TestInt*' && ./mvnw test '-Dtest=TestInt*'또는 Failsafe 및 Surefire 플러그인 사용). - 외부 종속성과의 통합이 중요하지 않거나 건너뛸 수 있는 시스템 동작("코드" 줄 )을 테스트할 때는 모의 테스트를 사용하세요.
- 통합 테스트를 사용하여 외부 시스템에 대한 가정과 통합을 검증하세요.
- 위의 요점에 따라 두 가지 접근 방식을 모두 사용하는 것이 합리적이라면 테스트하는 것을 두려워하지 마세요.
버전(저희의 경우 8.15.x)을 너무 엄격하게 적용하는 것은 지나치다는 지적이 있을 수 있습니다. 버전 태그만 사용할 수도 있지만, 이 글에서는 버전 간에 변경될 수 있는 다른 모든 기능을 나타내는 역할을 한다는 점에 유의하세요.
시리즈의 다음 편에서는 테스트 데이터 세트를 사용해 테스트 컨테이너에서 실행 중인 Elasticsearch를 초기화하는 방법을 살펴보겠습니다. 이 블로그를 기반으로 구축한 것이 있거나 궁금한 점이 있으면 토론 포럼 및 커뮤니티 Slack 채널에 알려주세요.

