스택큐힙리스트

멀티모듈 테스트 격리 & Fixture 전략 본문

개발

멀티모듈 테스트 격리 & Fixture 전략

스택큐힙리스트 2025. 7. 12. 00:13
반응형

왜 멀티모듈에서 테스트 격리가 더 어려울까?

모놀리식 프로젝트는 src/test 한 곳만 관리하면 되지만, 멀티모듈은 컨텍스트가 분리되어 있어

  • 모듈 간 의존성 루프
  • 무겁고 느린 공통 SpringBootTest
  • 서로 다른 DB 스키마·데이터 충돌
    같은 문제가 폭발적으로 늘어납니다. 오늘은 실제 현업에서 검증된 테스트 격리Fixture 공유 노하우를 정리합니다.

1️⃣ 테스트 유형별 분리 기준

  • Unit Test : 모듈 내부 클래스만 테스트, JUnit5 + Mockito.
  • Slice Test : @DataJpaTest, @WebMvcTest 등 스프링 슬라이스.
  • Integration Test : 여러 모듈 협업 + 외부 시스템(Testcontainers).
  • E2E Test : API → DB → 메시징 전 구간 검증(Cypress).

Rule of Thumb — “단일 모듈 책임은 Unit/Slice, 모듈 협업은 Integration”로 경계를 명확히!


2️⃣ Gradle testFixtures 플러그인으로 공통 Fixture 공유

  1. 모듈 설정
    plugins { id("java-test-fixtures") }
  2. 공유 코드 배치
    src/testFixtures/java 에 DummyMember, SampleOrder 같은 더미 엔티티·빌더 클래스 저장.
  3. 의존성 선언 — 다른 모듈에서
    testImplementation(testFixtures(project(":core")))
  4. 장점
    • 프로덕션 코드 오염 X
    • 컴파일 타임 의존성만 추가 → 빌드 속도 유지
    • 모듈별 서로 다른 Fixture 버전 관리 가능

 


3️⃣ Testcontainers로 모듈별 격리 DB 구동

@Testcontainers
class MemberRepositoryTest {
    @Container
    val maria = MariaDBContainer("mariadb:11").apply {
        withDatabaseName("member_db")
    }
}
  • 모듈마다 고유 데이터베이스명 → 데이터 충돌 방지
  • CI에서만 컨테이너 기동, 로컬은 H2 등 인메모리로 대체하는 profile 전략 추천

4️⃣ @DynamicPropertySource로 런타임 설정 주입

@DynamicPropertySource
fun dbProps(registry: DynamicPropertyRegistry) {
    registry.add("spring.datasource.url") { maria.jdbcUrl }
}
  • 모듈이 다르더라도 동일한 패턴으로 DB·Redis·Kafka URI를 주입
  • 스프링 컨텍스트 재사용이 불가능한 경우에도, 매 테스트마다 정확한 환경 보장

5️⃣ Mock Server( WireMock )로 외부 API 고립

멀티모듈 프로젝트에서 api 모듈은 외부 결제·SMS 서버를 호출합니다.

  • @AutoConfigureWireMock(port = 0)
  • 고정 stub 파일을 src/test/resources/mappings 에 배치
  • 테스트 완료 후 자동 종료 → 다른 모듈과 포트 충돌 없음

6️⃣ 테스트 네이밍 & 폴더 컨벤션

core
 └─ src
    └─ test
       ├─ unit
       ├─ slice
       └─ integration
  • 폴더 이름이 테스트 레벨을 드러내면 리뷰·코드 탐색 속도가 올라감
  • Gradle --tests unit.* 식으로 레벨별 선택 실행 가능

7️⃣ CI에서 병렬 실행 + 캐시

  • ./gradlew test --parallel --continue
  • GitHub Actions strategy.matrix 로 모듈별 Job 분리 → 실패 모듈만 재시도
  • org.gradle.caching=true 를 켜고, 필요 시 원격 캐시 서버 연결

8️⃣ 흔히 놓치는 Pitfall 3

  1. 테스트 전용 설정 클래스의 패키지가 public 모듈로 노출돼 의존성 루프 발행
  2. Fixture에 @Component 등록 → 프로덕션 빈 스캔에 끼어들어 런타임 오류
  3. 공통 Embedded Redis 포트 중복 → 각 모듈마다 @DynamicPropertySource로 난수 포트 지정

✨ 정리

멀티모듈 환경에선 “작게 쪼개고, 확실히 격리”가 생존 전략입니다.

  • testFixtures로 중복 코드 제로
  • Testcontainers & WireMock으로 외부 의존성 차단
  • 폴더·CI 분리로 빌드 시간 단축

이제 테스트가 빌드의 병목이 아닌, 품질 속도를 높이는 엔진이 될 것입니다. 다음 글에서는 GitHub Actions + Testcontainers 캐시 최적화를 다룰 예정이니 기대하세요!

반응형
Comments