728x90
반응형
Spring에서 Junit을 사용하여 Elasticsearch 테스트 코드를 작성하는 방법
Spring 프로젝트에서 Junit으로 Test Code를 작성할 때 RDB(Mysql, Postgresql, Orcle 등)는 JPA나 MyBatis의 Queyr(method)를 사용하여 결과값을 간단히 검증할 수 있지만 Elasticsearch의 경우 org.elasticsearch에서 제공하는 Library를 활용하여 TEST CODE를 효율적으로 작성할 수 있다.
기존에는 ES 임베디드 환경을 사용하여 테스트가 가능했지만 7.0 버전부터 사용이 불가능해진 것으로 보이며 ES의 개발자가 대안으로 RestHighLevelClient를 사용한 In Memory 테스트 방법을 추천한다.
https://discuss.elastic.co/t/es-embedded-in-7-0/219823
통합(Integration) 테스트 예시
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2017 the original author or authors.
*/
package es.example.test.integration;
import static org.assertj.core.api.BDDAssertions.then;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.elasticsearch.test.ESIntegTestCase.Scope;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
@ClusterScope(scope = Scope.SUITE) // TestSuite에 대한 Elasticsearch 클러스터의 범위 지정
@ThreadLeakScope(ThreadLeakScope.Scope.NONE) // 테스트 중에 Thread 누수가 없어야 함을 지정
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
public class EsIntegrationTest extends ESIntegTestCase {
private static final String TRACE_ID_FIELD = "traceID";
private static final String TRACE_TYPE = "trace";
private static final String OPEN_TRACE_INDEX = "traces";
private static final String OPERATION_NAME_FIELD = "operationName";
private Client client;
@Override
@Before
public void setUp() throws Exception { // setup 함수를 override하여 client 세팅
super.setUp();
this.client = client();
}
// ESIntegTestCase는 자체 AssertThat을 정의하는 junit Assert에서 상속되므로
// AssertJ AssertThat을 사용할 수 없지만 BDDAssertions.then을 사용할 수 있다. :)
@Test //Todo. operationName 기준 TEST CASE
public void search_open_tracing_traces_by_operationName() throws Exception {
// GIVEN
final String operationName = randomOperationName();
indexOpenTraceDocument(randomTraceID(), operationName);
indexOpenTraceDocument(randomTraceID(), operationName);
// WHEN
final SearchResponse response = searchOpenTracesByOperationName(operationName);
// THEN
then(response.getHits())
.hasSize(2)
.extracting(SearchHit::getSourceAsMap)
.allSatisfy(hit -> then(hit).containsEntry(OPERATION_NAME_FIELD, operationName));
}
@Test //Todo. traceID 기준 TEST CASE
public void search_open_tracing_traces_by_traceID() throws Exception {
// GIVEN
final String traceID = randomTraceID();
indexOpenTraceDocument(traceID, randomOperationName());
indexOpenTraceDocument(traceID, randomOperationName());
// WHEN
final SearchResponse response = searchOpenTracesByTraceID(traceID);
// THEN
then(response.getHits())
.hasSize(2)
.extracting(SearchHit::getSourceAsMap)
.allSatisfy(hit -> then(hit).containsEntry(TRACE_ID_FIELD, traceID));
}
private String randomTraceID() {
return randomAlphaOfLengthBetween(1, 50);
}
private String randomOperationName() {
return randomAlphaOfLengthBetween(1, 50);
}
// operationName으로 데이터 조회
private SearchResponse searchOpenTracesByOperationName(final String operationName) {
return this.client.prepareSearch(OPEN_TRACE_INDEX)
.setTypes(TRACE_TYPE)
.setQuery(QueryBuilders.commonTermsQuery(OPERATION_NAME_FIELD, operationName))
.get();
}
// traceID로 데이터 조회
private SearchResponse searchOpenTracesByTraceID(final String traceID) {
return this.client.prepareSearch(OPEN_TRACE_INDEX)
.setTypes(TRACE_TYPE)
.setQuery(QueryBuilders.commonTermsQuery(TRACE_ID_FIELD, traceID))
.get();
}
// 초기 Test 데이터 생성 함수.
private void indexOpenTraceDocument(final String traceID, final String operationName) throws Exception {
this.client.prepareIndex(OPEN_TRACE_INDEX, TRACE_TYPE)
.setSource(generateOpenTrace(traceID, operationName), XContentType.JSON)
.execute()
.get();
// refreshes the index otherwise we would not find anything
refresh();
}
// 테스트 데이터 반환
private static String generateOpenTrace(final String traceID, final String operationName) {
return "{\n" +
" \"traceID\": \"" + traceID + "\",\n" +
" \"spanID\": \"3b1237777ef2d83\",\n" +
" \"parentSpanID\": \"bbe20e919b94f710\",\n" +
" \"operationName\": \"" + operationName + "\",\n" +
" \"references\": [],\n" +
" \"startTime\": 1510878645507000,\n" +
" \"duration\": 129000,\n" +
" \"tags\": [\n" +
" {\n" +
" \"key\": \"mvc.controller.class\",\n" +
" \"type\": \"string\",\n" +
" \"value\": \"Apis\"\n" +
" },\n" +
" {\n" +
" \"key\": \"mvc.controller.method\",\n" +
" \"type\": \"string\",\n" +
" \"value\": \"pong\"\n" +
" },\n" +
" {\n" +
" \"key\": \"source\",\n" +
" \"type\": \"string\",\n" +
" \"value\": \"KevinWasPong\"\n" +
" },\n" +
" {\n" +
" \"key\": \"spring.instance_id\",\n" +
" \"type\": \"string\",\n" +
" \"value\": \"172.20.41.251:Service2:18081\"\n" +
" },\n" +
" {\n" +
" \"key\": \"span.kind\",\n" +
" \"type\": \"string\",\n" +
" \"value\": \"server\"\n" +
" }\n" +
" ],\n" +
" \"logs\": [],\n" +
" \"processID\": \"\",\n" +
" \"process\": {\n" +
" \"serviceName\": \"service2\",\n" +
" \"tags\": [\n" +
" {\n" +
" \"key\": \"ip\",\n" +
" \"type\": \"int64\",\n" +
" \"value\": \"-1407964677\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"warnings\": null,\n" +
" \"startTimeMillis\": 1510878645507\n" +
" }";
}
}
※Test Code 설명
ESIntegTestCase 확장:
해당 클래스는 Elasticsearch의 통합 테스트를 위해 제공되는 ESIntegTestCase를 확장한다.
설정 메서드:
setUp 메서드가 Elasticsearch 클라이언트를 초기화하기 위해 override한다.
테스트 메서드:
searchOpenTracesByOperationName 및 searchOpenTracesByTraceID 메서드는 각각 operationName, traceID를 기반 으로 검색을 수행하고, AssertJ의 BDDAssertions를 사용하여 검색 결과에 대한 어설션을 수행한다.
문서 인덱싱:
indexOpenTraceDocument 메서드는 Spring Data Elasticsearch 클라이언트를 사용하여 Elasticsearch에 open trace 문서를 인덱싱합니다.
데이터 생성:
generateOpenTrace 메서드는 traceID, spanID, operationName 및 tag와 같은 다양한 필드를 포함하는 open trace을 나타내는 샘플 JSON 문서를 생성한다.
라이선스 정보:
코드는 Apache License, Version 2.0에 따라 라이선스가 부여되었음을 시작 부분에 명시한다.
유닛(Unit) 테스트 예시
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* Copyright 2017 the original author or authors.
*/
package es.example.test.unit;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.ESTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
public class EsUnitTest extends ESTestCase {
@Test
public void simpleEsTest() {
// GIVEN
String indexName = "example_index";
String documentId = "1";
String documentSource = "{\"field\": \"value\"}";
// WHEN
IndexRequest indexRequest = new IndexRequest(indexName)
.id(documentId)
.source(documentSource, XContentType.JSON);
IndexResponse indexResponse = client().index(indexRequest, COMMON_OPTIONS);
// THEN
assertNotNull(indexResponse);
assertEquals("created", indexResponse.getResult().name());
assertTrue(indexResponse.getVersion() > 0);
}
}
Github (source code)
- 끝 -
728x90
반응형
'📚Framework & Library > JUnit' 카테고리의 다른 글
[Junit] Jasper Report PDF 문서 검증하기 (feat. JpdfUnit) (2) | 2024.01.23 |
---|---|
[Junit] Test 종류. Test Case (feat. 전자정부 프레임워크) (0) | 2023.11.17 |
[Junit] TypeReference, JsonPath - Json 다루기 (feat. ObjectMapper) (3) | 2023.11.17 |
[Junit] Junit4 다양한 라이브러리 활용 (feat. AssertJ, Unitils, RestAssured) (2) | 2023.11.17 |
[JUnit] junit5 간단한 테스트 코드 작성 (0) | 2023.09.10 |