Spring Boot + Testcontainers 테스트가 패키지 위치에 따라 깨졌던 이유와 해결 과정 정리

오병택·2026년 2월 10일
post-thumbnail

1. 문제 상황 요약

Spring Boot 프로젝트에서 회원가입 통합 테스트를 작성했고,
DB는 Testcontainers + MySQL을 사용하고 있었다.

  • 로컬에서는 테스트 코드가 정상 동작

  • GitHub Actions CI에서는 DB Connection refused

에러의 핵심 형태는 항상 동일했다.

CannotCreateTransactionException
 └─ JDBCConnectionException
    └─ CommunicationsException
       └─ java.net.ConnectException

겉으로 보기엔:

  • MySQL 컨테이너는 떠 있음

  • 포트도 로그에 출력됨

  • 그런데 Spring이 DB에 붙지 못함

2. 테스트 구성 요약

Testcontainers 베이스 클래스

@Testcontainers
@ActiveProfiles("test")
public abstract class MySQLContainerBaseTest {

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @DynamicPropertySource
    static void overrideProps(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", mysql::getJdbcUrl);
        registry.add("spring.datasource.username", mysql::getUsername);
        registry.add("spring.datasource.password", mysql::getPassword);
        registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop");
    }
}

회원가입 통합 테스트

@SpringBootTest
@Transactional
class AuthSignupIntegrationTest extends MySQLContainerBaseTest {
    ...
}

3. 처음 의심했던 것들 (그리고 전부 시도함)

1️⃣ 프로파일 문제 의심

  • application.yml에 profiles.default=dev 존재

  • CI / 테스트에서 dev로 빨려 들어가는 문제 의심

👉 시도한 것들

  • @ActiveProfiles("test")

  • -Dspring.profiles.active=test

  • CI에서 SPRING_PROFILES_ACTIVE=test

📌 결론

  • 프로파일 문제는 있었고 정리해야 할 문제는 맞음

  • 하지만 이 문제의 직접 원인은 아니었음

  • test 프로파일이 활성화돼도 증상은 그대로였음

2️⃣ Testcontainers 자체 문제 의심

  • Docker 실행 여부 확인

  • 컨테이너 상태 확인

  • Ryuk 컨테이너(Testcontainers 관련 컨테이너) 정상 동작 확인

  • MySQL 컨테이너 로그 확인

MYSQL CONTAINER RUNNING = true
JDBC URL: jdbc:mysql://localhost:xxxxx/testdb

-> 심지어 테스트 도중 컨테이너는 정상적으로 떠 있음

📌 결론

  • Testcontainers 자체 문제 ❌

  • Docker 문제 ❌

  • 컨테이너 생성 실패 ❌

3️⃣ abstract 누락 문제

베이스 테스트 클래스가 일반 클래스라
JUnit이 이 클래스 자체를 테스트로 인식할 가능성 의심

👉 해결

public abstract class MySQLContainerBaseTest { ... }

📌 결론

  • 이건 반드시 필요한 개선

  • 하지만 이것만으로 문제는 해결되지 않음

4️⃣ @SpringBootTest(classes=...) 문제

@SpringBootTest(classes = DeliveryPlatformApplication.class)
  • 메인 클래스를 명시해서

  • 패키지 위치에 따른 탐색 문제 차단 시도

📌 결론

  • 이것도 안 됨

  • classes를 지정해도 증상 동일

4. 결정적인 관찰 (가장 중요)

AuthSignupIntegrationTest의 패키지 위치에 따라 결과가 바뀐다

❌ 실패하는 위치

package com.example.deliveryplatform.auth;

✅ 성공하는 위치

package com.example.deliveryplatform;

📌 이게 핵심

  • 코드 동일

  • 설정 동일

    실행 명령 동일

  • 패키지 위치만 변경

-> 그런데 결과가 완전히 달라짐.

5. 왜 이런 현상이 생겼는가 (이론적 설명)

이 문제는 단일 원인 문제가 아니다.

겹쳐 있던 요소들

  • @SpringBootTest

  • @WebMvcTest (슬라이스 테스트도 함께 존재)

  • Spring Test ApplicationContext 캐싱

  • Testcontainers + DynamicPropertySource

  • 테스트 실행 순서 / 캐시 재사용

  • 테스트 클래스의 패키지 위치

핵심 포인트

  • Spring Test는 컨텍스트를 캐싱한다

  • 캐시 키는 단순히 @SpringBootTest 하나만으로 결정되지 않는다

  • 패키지 위치, 테스트 종류, 부트스트랩 방식이 컨텍스트 재사용에 영향을 준다

그 결과

  • 컨테이너는 떠 있지만

  • Spring이 이전 컨텍스트의 DataSource를 재사용하거나

  • 이미 종료된 커넥션을 참조하는 상태가 발생할 수 있다

-> 그래서

  • 컨테이너는 살아 있는데

  • HikariPool에서는 Connection refused

  • 종료 시점에만 에러가 터지는 기괴한 로그가 나온다

6. 왜 “루트 패키지에 두면” 해결됐나?

com.example.deliveryplatform (메인 클래스와 동일 패키지)
  • Spring Boot의 컴포넌트 스캔 기준점이 가장 명확

  • 테스트 컨텍스트 탐색/캐싱이 단순해짐

  • 슬라이스 테스트와의 캐시 충돌 가능성 감소

    📌 이건 우연이 아니라 구조적 안정성 차이

7. 최종적으로 선택한 구조

✅ 통합 테스트 전략

통합 테스트(@SpringBootTest)
→ 루트 패키지 기준 하위에 둔다

com.example.deliveryplatform
 └─ AuthSignupIntegrationTest

✅ 슬라이스 테스트 전략

@WebMvcTest 등은 도메인 패키지에 유지

com.example.deliveryplatform.auth
 └─ AuthControllerTest

✅ 공통 설정

  • 베이스 테스트 클래스는 abstract

  • CI에서는 profile 명시적으로 고정

  • profiles.default=dev 제거 (또는 최소화)

8. 정리된 결론

❓ 원인이 애매한가?

➡️ 단일 원인은 아니지만, 원인이 불분명한 것도 아니다

이건:

  • 실수 ❌

  • 설정 하나 빼먹음 ❌

  • Testcontainers 오용 ❌

대신:

  • Spring Boot 테스트 인프라의 “경계 구간”에서 발생한 구조적 문제

✔ 실무적으로 중요한 결론

  • 통합 테스트는 루트 패키지 기준으로 고정

  • 슬라이스 테스트와 컨텍스트 캐시를 구조로 분리

“왜 100% 그런지”보다
“다시는 안 흔들리게 만드는 구조”가 더 중요

9. 한 줄 요약

Testcontainers + SpringBootTest 환경에서
테스트 클래스의 패키지 위치는 단순한 정리가 아니라
컨텍스트 안정성을 좌우하는 요소였다.

AI는 아직 발전이 덜 됐다. 하루종일 찾다가 그전 커밋에는 괜찮았어서 커밋 푸시한 기록 중에 설마 패키지 위치 변경 때문이겠어? 했는데 맞았다. 내가 AI 이김! AI한테도 그전 커밋 푸시 중 문제 될만한 것이 있는지 확인 요청을 했으나 결론: 내가 이김

profile
걱정하지 말고 일단 해봐!

0개의 댓글