-
Spring batch integration test (feat.Elasticsearch)DEV 2023. 12. 2. 20:10
Purpose of Integration Test
- 여러 컴포넌트 간 상호작용이 정상적으로 수행되는지
- DAO가 올바르게 연결돼 있어서 원하는 데이터를 저장, 읽을 수 있는지?
Test Environment Setup
멱등성(idempotent) 유지
- 연산을 여러 번 적용하더라도 결과가 달라지지 않는 성질
- 멱등성이 깨지기 쉬운 구간 → 외부 모듈 → DB
- 개발 DB로 테스트를 하기엔 좀 그렇고, 테스트만을 위한 DB를 세팅하기도 좀..
DB 테스트 환경 구축 방법
- local DB 설치
- in-memory DB
- H2, 빠르지만 특정 DB에 특화된 기능 지원 x - 사용하는 DB의 Embedded Library 사용
- in-memory DB 이슈 해결
- embedded를 지원하지 않거나 특정 버전, OS지원 안 하는 경우 - docker compose
- docker-compose 파일 따로 관리
- docker 컨테이너와 통신 설정 어려움 - TestContainer
- docker compose와 동작 원리는 같지만 docker-compose 설정 x
- 코드만으로 docker 컨테이너를 사용한 테스트 환경 설정 컨테이너와 통신 부분도 언어 레벨에서 처리
ElasticSearch로 테스트 환경을 만들어 보자
- 아래 주소에서 Embedded 환경을 제공하지만, 더 이상 관리 되지 않고 있기 때문에,
- https://github.com/allegro/embedded-elasticsearch
- 권장하는 testcontainers를 사용
- https://java.testcontainers.org/modules/elasticsearch/
ElasticSearch TestContainer setup
Adding dependency
testImplementation "org.testcontainers:elasticsearch:1.16.3"
ESTestContainer.class
@TestConfiguration @EnableElasticsearchRepositories(basePackages = "com.k13e.muffin.module.common.repository") public class ESTestContainer extends AbstractElasticsearchConfiguration { private static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName .parse("docker.elastic.co/elasticsearch/elasticsearch") .withTag("7.16.2"); private static final ElasticsearchContainer container; static { container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE) .withExposedPorts(9200,9300) .withEnv("discovery.type","single-node"); container.start(); } @Override public RestHighLevelClient elasticsearchClient() { String hostAddress = new StringBuilder() .append(container.getHost()) .append(":") .append(container.getMappedPort(9200)) .toString(); ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo(hostAddress) .build(); return RestClients.create(clientConfiguration).rest(); } }
BatchJobTest.class
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {Configuration.class, ESTestContainer.class, }) @SpringBatchTest public class BatchJobTest { .... Test repository @Import(ESTestContainer.class) @SpringBootTest(classes = DataRepository.class) public class DataRepositoryTest { @Autowired DataRepository dataRepository; @Test void test() { //given final Data d = Data.builder() .id("test") .active(true) .build(); dataRepository.save(d); //when final Optional<Data> test = dataRepository.findById("test"); final Stream<Data> test1 = dataRepository.findDataByActiveIsTrue(); //then assertThat(test.get().getId()).isEqualTo(d.getId()); assertThat(test1.findFirst().get().getId()).isEqualTo(d.getId()); } }
Test Spring batch
- Spring batch에서 step scope, job scope → 유용한 도구
- 하지만 테스트 시 까다로울 수 있다
- step scope 바깥에서 해당 컴포넌트를 실행했을 때 의존성 문제
Spring batch component
- batch job 시작 → jobInstance 생성 ⇒ jobInstance = jobName + jobParameter
- 동일한 jobParmeter로 동일한 job을 두 번 이상 실행 할 수 x
- jobInstance → 잡의 논리적 실행
- job : jobInstance = 1 : N - jobExecution → 잡 실행의 실제 시도
- jobInstance : jobExecution = 1 : N - jobParameter → 배치 작업이 수행될 때마다 전달되는 Parameter
- ex: dataType, run.id..
JobScope, StepScope 하는 일
- @JobScope는 step에 선언가능
- @StepScope는 itemReader, itemWriter, itemProcessor, tasklet에 사용 가능
- 선언한 bean의 생성을 지연 (late-binding)
- controller, service 같은 비즈니스 로직 레벨에서 jobParmeter를 할당할 수 있다.
jobParameter 주입
- JobExecution, StepExcution에서 jobParmeter를 가져와 빈에 주입
그럼 itemReader를 테스트하기 위해서는?
- 테스트 환경에 StepExecution을 생성 후, 필요한 jobParmeter를 설정
@ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {Configuration.class, TestBatchConfig.class, ESTestContainer.class }) @SpringBatchTest public class ApiItemReaderTest { @Autowired private DataRepository dataRepository; @Autowired private ItemReader<LinkedHashMap> itemReader; private String id = "test"; @BeforeEach public void setup(){ dataRepository.save(getData(id)); } public StepExecution getStepExecution() { JobParameters jobParameters = new JobParametersBuilder() .addString("id", "test") .toJobParameters(); return MetaDataInstanceFactory.createStepExecution(jobParameters); } @Test public void test() throws Exception { final Optional<Data> savedData = dataRepository.findById(id); assertNotNull(itemReader.read()); assertEquals(savedData.get().getId(), id); } }
@TestExecutionListener
- 테스트 메서드 실행 전후에 수행돼야 하는 일을 정의하는 스프링 API
- @BeforeEach, @AfterEach 보다 재사용성이 좋고, 테스트 케이스의 모든 메서드에 원하는 동작을 재사용 가능한 방식으로 삽입 가능
StepScopeTestExecutionListener
- 테스트 케이스에서 StepExecution을 가져오고, 콘텍스트를 현재 테스트메서드의 컨텍스트로 사용
- 모든 테스트 메서드가 실행되는 동안 스텝 컨텍스트를 제공
Job, Step를 테스트하려면
- jobLauncherTestUtils.launchJob(jobParameters);
- jobLauncherTestUtils.launchStep("test", jobParameters);
# Reference
- https://docs.spring.io/spring-batch/docs/current/reference/html/index.html
- https://medium.com/riiid-teamblog-kr/testcontainer-로-멱등성있는-integration-test-환경-구축하기-4a6287551a31
- https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/ja/Ch02_SpringBatchArchitecture.html
728x90'DEV' 카테고리의 다른 글
Spring batch jobScope, stepScope (0) 2023.12.02 Elasticsearch에서의 relation (1) 2023.12.02 K8S, DNS 간헐적 5~15초 지연 (0) 2023.12.02 Test Double (1) 2023.12.02 Dependency Mechanism (1) 2023.12.02