안녕하세요, Code Camp 독자 여러분!
지난 시간에는 배치의 성능을 극한으로 끌어올리는 방법에 대해 이야기했었죠. 하지만 속도보다 더 중요한 것이 있습니다. 바로 ‘안전성’입니다.
수백만 건의 데이터를 처리하는 도중에 코드가 멈추거나, 잘못된 계산 결과가 DB에 저장된다면? 상상만 해도 끔찍한 일입니다.
오늘은 여러분의 배치가 365일 안전하게 돌아갈 수 있도록 돕는 Spring Batch 테스트 전략의 모든 것을 파헤쳐 보겠습니다.

목차
- 배치 테스트가 어려운 이유와 해결책
- 단위 테스트: Reader, Writer, Processor 고립시키기
- 통합 테스트의 핵심: @SpringBatchTest와 JobLauncherTestUtils
- Scope가 적용된 빈(Bean) 테스트하기
- 테스트 속도 향상: 가벼운 환경 구성 팁
- 실무 트러블슈팅: Dirty Context와 리소스 정리
1. 배치 테스트가 어려운 이유와 해결책
일반적인 웹 애플리케이션 테스트와 달리 배치 테스트는 몇 가지 진입 장벽이 있습니다.
- 복잡한 설정: Job, Step, Reader, Writer 등 수많은 빈(Bean)들이 얽혀 있습니다.
- 실행 시간: 전체 Job을 실행하려면 시간이 오래 걸립니다.
- 데이터 의존성: DB에 특정 상태의 데이터가 있어야만 테스트가 가능한 경우가 많습니다.
해결책:
우리는 ‘피라미드 테스트 전략’을 사용해야 합니다.
– Unit Test (70%): 비즈니스 로직(Processor)만 떼어내어 빠르게 검증.
– Slice Test (20%): 특정 Step 하나만 실행하여 Reader/Writer 연결 검증.
– End-to-End Test (10%): 전체 Job 실행 검증.
2. 단위 테스트: Reader, Writer, Processor 고립시키기
가장 쉬우면서도 효과적인 방법은 비즈니스 로직이 들어있는 ItemProcessor를 POJO(Plain Old Java Object)로 보고 테스트하는 것입니다. Spring 컨텍스트를 띄울 필요가 없어 속도가 매우 빠릅니다.
예제: VIP 고객 선정 로직 테스트
// 비즈니스 로직 (POJO)
public class VipUserProcessor implements ItemProcessor<User, User> {
@Override
public User process(User user) {
if (user.getTotalAmount() >= 1_000_000) {
user.setGrade(UserGrade.VIP);
return user;
}
return null; // 100만원 미만은 필터링
}
}
// 단위 테스트 (JUnit 5)
class VipUserProcessorTest {
@Test
void 구매금액이_100만원_이상이면_VIP가_된다() {
// given
VipUserProcessor processor = new VipUserProcessor();
User user = new User("tester", 1_000_000);
// when
User result = processor.process(user);
// then
assertThat(result).isNotNull();
assertThat(result.getGrade()).isEqualTo(UserGrade.VIP);
}
}

3. 통합 테스트의 핵심: @SpringBatchTest와 JobLauncherTestUtils
Spring Batch 4.1부터 도입된 @SpringBatchTest 어노테이션은 테스트 설정을 획기적으로 줄여줍니다.
이 어노테이션을 사용하면 JobLauncherTestUtils와 JobRepositoryTestUtils를 자동으로 주입받을 수 있습니다.
전체 Job이 아닌 ‘특정 Step’만 실행하기
배치 테스트의 핵심은 Job 전체를 돌리지 않고, 내가 수정한 Step 하나만 돌리는 것입니다.
@SpringBatchTest
@SpringBootTest(classes = {VipUserJobConfig.class, TestBatchConfig.class})
class VipUserJobTest {
@Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
@Autowired
private JobRepositoryTestUtils jobRepositoryTestUtils;
@AfterEach
public void tearDown() {
jobRepositoryTestUtils.removeJobExecutions(); // 테스트 데이터 정리
}
@Test
void VIP_선정_Step만_실행_테스트() {
// given: 테스트용 JobParameter 설정
JobParameters jobParameters = new JobParametersBuilder()
.addString("targetDate", "2024-01-01")
.toJobParameters();
// when: 특정 Step 실행
JobExecution jobExecution = jobLauncherTestUtils.launchStep("vipUserStep", jobParameters);
// then
assertThat(jobExecution.getStatus()).isEqualTo(BatchStatus.COMPLETED);
}
}
4. Scope가 적용된 빈(Bean) 테스트하기
@StepScope나 @JobScope가 붙은 빈은 Job 실행 시점에 생성되기 때문에, 일반적인 단위 테스트로는 빈을 가져올 수 없어 에러(ScopeNotActiveException)가 발생합니다.
이때는 StepScopeTestUtils라는 유틸리티를 사용해야 합니다.
@Test
void StepScope_Reader_테스트() throws Exception {
// given
JbdcPagingItemReader<User> reader = vipUserJobConfig.vipUserReader(null); // 파라미터 null 주입
// when
int count = StepScopeTestUtils.doInStepScope(
stepExecution,
() -> {
reader.open(new ExecutionContext());
int readCount = 0;
while (reader.read() != null) {
readCount++;
}
return readCount;
}
);
// then
assertThat(count).isEqualTo(5); // DB에 5명 있다고 가정
}
5. 테스트 속도 향상: 가벼운 환경 구성 팁
배치 통합 테스트가 느린 주범은 @SpringBootTest가 무거운 애플리케이션 컨텍스트를 띄우기 때문입니다.
- 최소한의 설정 로드:
@SpringBootTest에classes = ...를 명시하여, 테스트하려는 Job 설정 파일과 필수 인프라 설정만 로드하세요. 전체 앱을 띄우지 마세요. - H2 DB 사용: 테스트는 인메모리 DB(H2)에서 수행하여 I/O 비용을 최소화하세요.
6. 실무 트러블슈팅: Dirty Context와 리소스 정리
문제: 테스트 간 간섭 발생
테스트 A에서 변경한 싱글톤 빈의 상태가 테스트 B에 영향을 주는 경우입니다. 특히 배치에서는 JobLauncher 등이 공유될 때 문제가 됩니다.
해결책: @DirtiesContext
테스트 메서드나 클래스에 @DirtiesContext를 붙이면, 해당 테스트가 끝난 후 스프링 컨텍스트를 강제로 재생성합니다. 속도는 느려지지만, 테스트 격리성을 확실하게 보장합니다.
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
class ComplexJobTest {
// ...
}
마치며
“테스트 코드가 없는 배치는 시한폭탄과 같다.”
제가 주니어 시절 선배님께 들었던 말입니다.
오늘 소개한 JobLauncherTestUtils와 단위 테스트 기법을 활용한다면, 수정 사항이 생겨도 두려움 없이 배포할 수 있는 탄탄한 시스템을 만들 수 있을 것입니다.
다음 시간에는 Spring Batch와 Jenkins/Airflow를 연동하는 스케줄링 전략에 대해 알아보겠습니다.
Code Camp와 함께, 오늘도 안전한 배포 되세요!





![[Spring Batch] 스프링 배치에서 csv, txt 파일 읽기(FlatFileItemReader)](https://codecampai.com/wp-content/uploads/2026/01/image-42-768x419.jpg)
