
Spring Boot 프로젝트에서 회원가입 통합 테스트를 작성했고,
DB는 Testcontainers + MySQL을 사용하고 있었다.
로컬에서는 테스트 코드가 정상 동작
GitHub Actions CI에서는 DB Connection refused
에러의 핵심 형태는 항상 동일했다.
CannotCreateTransactionException
└─ JDBCConnectionException
└─ CommunicationsException
└─ java.net.ConnectException
겉으로 보기엔:
MySQL 컨테이너는 떠 있음
포트도 로그에 출력됨
그런데 Spring이 DB에 붙지 못함
@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 {
...
}
application.yml에 profiles.default=dev 존재
CI / 테스트에서 dev로 빨려 들어가는 문제 의심
@ActiveProfiles("test")
-Dspring.profiles.active=test
CI에서 SPRING_PROFILES_ACTIVE=test
프로파일 문제는 있었고 정리해야 할 문제는 맞음
하지만 이 문제의 직접 원인은 아니었음
test 프로파일이 활성화돼도 증상은 그대로였음
Docker 실행 여부 확인
컨테이너 상태 확인
Ryuk 컨테이너(Testcontainers 관련 컨테이너) 정상 동작 확인
MySQL 컨테이너 로그 확인
MYSQL CONTAINER RUNNING = true
JDBC URL: jdbc:mysql://localhost:xxxxx/testdb
-> 심지어 테스트 도중 컨테이너는 정상적으로 떠 있음
Testcontainers 자체 문제 ❌
Docker 문제 ❌
컨테이너 생성 실패 ❌
베이스 테스트 클래스가 일반 클래스라
JUnit이 이 클래스 자체를 테스트로 인식할 가능성 의심
public abstract class MySQLContainerBaseTest { ... }
이건 반드시 필요한 개선
하지만 이것만으로 문제는 해결되지 않음
@SpringBootTest(classes = DeliveryPlatformApplication.class)
메인 클래스를 명시해서
패키지 위치에 따른 탐색 문제 차단 시도
이것도 안 됨
classes를 지정해도 증상 동일
AuthSignupIntegrationTest의 패키지 위치에 따라 결과가 바뀐다
package com.example.deliveryplatform.auth;
package com.example.deliveryplatform;
코드 동일
설정 동일
실행 명령 동일
패키지 위치만 변경
-> 그런데 결과가 완전히 달라짐.
이 문제는 단일 원인 문제가 아니다.
@SpringBootTest
@WebMvcTest (슬라이스 테스트도 함께 존재)
Spring Test ApplicationContext 캐싱
Testcontainers + DynamicPropertySource
테스트 실행 순서 / 캐시 재사용
테스트 클래스의 패키지 위치
Spring Test는 컨텍스트를 캐싱한다
캐시 키는 단순히 @SpringBootTest 하나만으로 결정되지 않는다
패키지 위치, 테스트 종류, 부트스트랩 방식이 컨텍스트 재사용에 영향을 준다
컨테이너는 떠 있지만
Spring이 이전 컨텍스트의 DataSource를 재사용하거나
이미 종료된 커넥션을 참조하는 상태가 발생할 수 있다
컨테이너는 살아 있는데
HikariPool에서는 Connection refused
종료 시점에만 에러가 터지는 기괴한 로그가 나온다
com.example.deliveryplatform (메인 클래스와 동일 패키지)
Spring Boot의 컴포넌트 스캔 기준점이 가장 명확
테스트 컨텍스트 탐색/캐싱이 단순해짐
슬라이스 테스트와의 캐시 충돌 가능성 감소
📌 이건 우연이 아니라 구조적 안정성 차이
통합 테스트(@SpringBootTest)
→ 루트 패키지 기준 하위에 둔다
com.example.deliveryplatform
└─ AuthSignupIntegrationTest
@WebMvcTest 등은 도메인 패키지에 유지
com.example.deliveryplatform.auth
└─ AuthControllerTest
베이스 테스트 클래스는 abstract
CI에서는 profile 명시적으로 고정
profiles.default=dev 제거 (또는 최소화)
➡️ 단일 원인은 아니지만, 원인이 불분명한 것도 아니다
이건:
실수 ❌
설정 하나 빼먹음 ❌
Testcontainers 오용 ❌
대신:
통합 테스트는 루트 패키지 기준으로 고정
슬라이스 테스트와 컨텍스트 캐시를 구조로 분리
“왜 100% 그런지”보다
“다시는 안 흔들리게 만드는 구조”가 더 중요
Testcontainers + SpringBootTest 환경에서
테스트 클래스의 패키지 위치는 단순한 정리가 아니라
컨텍스트 안정성을 좌우하는 요소였다.
AI는 아직 발전이 덜 됐다. 하루종일 찾다가 그전 커밋에는 괜찮았어서 커밋 푸시한 기록 중에 설마 패키지 위치 변경 때문이겠어? 했는데 맞았다. 내가 AI 이김! AI한테도 그전 커밋 푸시 중 문제 될만한 것이 있는지 확인 요청을 했으나 결론: 내가 이김