본문 바로가기

엘라스틱 서치

스프링부트 테스트와 엘라스틱서치 테스트 컨테이너

0. 개요

Spring Boot Test에서 엘라스틱서치 테스트 컨테이너를 띄워 테스트 해 보겠습니다.

nori도 사용할 것입니다.

 

1. 테스트

우선 테스트 엘라스틱 컨테이너를 띄워야 합니다.

이를 위해 의존성을 주입 받겠습니다.

// build.gradle
testImplementation("org.testcontainers:junit-jupiter:1.16.3")

 

테스트 파일 위치에 아래와 같이 만들겠습니다.

자세한 설명은 주석으로 달겠습니다.

@TestConfiguration
@EnableElasticsearchRepositories(basePackageClasses = UserSearchRepository.class)
public class ElasticTest extends AbstractElasticsearchConfiguration {

    // nori를 다운받기 위해서 DockerImageName을 사용하지 않고 그보더 더 추상화된 GenericContainer를 사용했습니다.
    private static final GenericContainer container;

    static{
        container = new GenericContainer(
                new ImageFromDockerfile().withDockerfileFromBuilder(
                        b -> {
                            b.from("docker.elastic.co/elasticsearch/elasticsearch:7.10.2") // 엘라스틱 이미지입니다.
                                    .run("bin/elasticsearch-plugin install analysis-nori") // 이미지에 nori를 다운 받습니다.
                                    .build();
                        })
        ).withExposedPorts(9200, 9300)
                .withFileSystemBind(
                        "es/settings/stop/english.txt",
                        "/usr/share/elasticsearch/config/settings/stop/english.txt", // 불용어 파일을 추가합니다.
                        BindMode.READ_ONLY)
                .withFileSystemBind(
                        "es/settings/synonym/english.txt",
                        "/usr/share/elasticsearch/config/settings/synonym/english.txt", // 동의어 파일을 추가합니다.
                        BindMode.READ_ONLY)
                .withEnv("discovery.type","single-node");
        container.start();
    }

    //엘라스틱서치를 사용하기 위해서는 high와 low level을 사용하는데, 주로 high level을 사용합니다.
    @Override
    public RestHighLevelClient elasticsearchClient() {
        String hostAddress = new StringBuilder()
                .append(container.getHost())
                .append(":")
                .append(container.getMappedPort(9200))
                .toString();

        ClientConfiguration configuration = ClientConfiguration.builder()
                .connectedTo(hostAddress)
                .build();
        return RestClients.create(configuration).rest();
    }
}

 

컨테이너를 가져와서 사용할 테스트 파일을 만듭니다.

@Import(ElasticTest.class)
@SpringBootTest(classes = UserSearchRepository.class)
public class ESRepositoryTest {

    @Autowired
    UserSearchRepository repository;

    @Test
    void test() {
        // given
        RequestUserSaveDto info = RequestUserSaveDto
                .builder()
                .id(1L)
                .name("ksb")
                .age(20L)
                .korean("하이 케이 에스 비")
                .english("hello ksb")
                .build();

        UserDocument user = UserDocument.of(info);

        // when
        repository.save(user);

        //then
        UserDocument result = repository.findByName("ksb").get(0);

        assertThat(result.getId()).isEqualTo(user.getId());
        assertThat(result.getAge()).isEqualTo(user.getAge());
        assertThat(result.getKorean()).isEqualTo(user.getKorean());
        assertThat(result.getEnglish()).isEqualTo(user.getEnglish());
    }
}

 

이제 필요 클래스를 하나씩 만듭니다.

우선 dto 먼저 만듭니다.

@AllArgsConstructor
@Getter
@Builder
public class RequestUserSaveDto {

    private final Long id;
    private final String name;
    private final Long age;
    private final String korean;
    private final String english;
}

 

domain을 만듭니다.

@Document(indexName = "user_index")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@Getter
@Setting(settingPath = "elastic/es-setting.json")
@Mapping(mappingPath = "elastic/es-mapping.json")
public class UserDocument {

    private Long id;
    private String name;
    private Long age;
    private String korean;
    private String english;

    @Field(type = FieldType.Date, format = DateFormat.custom, pattern = "uuuu-MM-dd'T'HH:mm:ss")
    private LocalDateTime created;

    public static UserDocument of(RequestUserSaveDto dto) {
        return UserDocument.builder()
                .id(dto.getId())
                .name(dto.getName())
                .age(dto.getAge())
                .korean(dto.getKorean())
                .english(dto.getEnglish())
                .created(LocalDateTime.now())
                .build();
    }
}

 

repository를 만듭니다.

public interface UserSearchRepository extends ElasticsearchRepository<UserDocument, Long> {

    List<UserDocument> findByName(String name);
}

끝.

 

이 글은 제가 만든 프로젝트의 일부분을 가져와서 작성했습니다.

자세한 파일 위치나 내용을 원하시면 아래 링크로 확인하시면 됩니다.

https://github.com/kang-seongbeom/ES-SpringBoot

 

GitHub - kang-seongbeom/ES-SpringBoot

Contribute to kang-seongbeom/ES-SpringBoot development by creating an account on GitHub.

github.com

 

이 테스트를 작성하기 까지 몇 가지 에러를 만날수 있었는데,

해당 트러블 슈팅은 아래 링크를 통해 남겨두겠습니다.

https://ksb-dev.tistory.com/315

 

[Spring Boot] Caused by: RestStatusException{status=400} org.springframework.data.elasticsearch.RestStatusException: Elasticsear

Spring Boot에서 Elasticsearch 테스트 컨테이너를 띄우는 도중 위와 같은 문제를 만났습니다. 코드는 아래와 같습니다. @TestConfiguration @EnableElasticsearchRepositories(basePackageClasses = {UserSearchRepository.class,UserS

ksb-dev.tistory.com

https://ksb-dev.tistory.com/316

 

[Spring Boot] nested exception is ElasticsearchException[Elasticsearch exception [type=no_such_file_exception, reason=/usr/share

이 글은 이전 글과 연관되어 있습니다. 저는 불용어(stopword)와 동의어(synonym)를 별도의 파일로 관리하고 있습니다. 아래의 코드로 nori 분석기를 설치할 수 있었으나, 불용어와 동의어 파일을 불러

ksb-dev.tistory.com