ITN Dev 07-22
fix: DB 테스트 하네스에 Testcontainers 싱글턴 컨테이너 패턴 적용
@Testcontainers/@Container를 쓰면 클래스마다 컨테이너가 stop되는데 Spring
컨텍스트 캐시는 죽은 포트를 가리키는 DataSource를 재사용해, 전체 스위트
실행 시 두 번째로 실행되는 DB 테스트 클래스가 순서에 따라 Connection
refused로 깨졌다. 컨테이너를 static 블록에서 한 번만 start하고 절대
stop하지 않도록 바꿔 모든 AbstractDbTest 하위 클래스가 동일한 컨텍스트/
컨테이너를 공유하게 했다. SchemaMigrationTest는 자체 스캐폴딩을 지우고
AbstractDbTest를 상속하도록 정리했다.
@27f606ddeb6b0b8f2bd3ea16b3880c59eab707bb
src/test/java/kr/itn/itnhub/AbstractDbTest.java
--- src/test/java/kr/itn/itnhub/AbstractDbTest.java
+++ src/test/java/kr/itn/itnhub/AbstractDbTest.java
@@ -5,8 +5,6 @@
 import org.springframework.test.context.DynamicPropertyRegistry;
 import org.springframework.test.context.DynamicPropertySource;
 import org.testcontainers.containers.PostgreSQLContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
 
 /**
  * Spring 컨텍스트를 실제로 로딩해야 하는 테스트의 공통 베이스.
@@ -14,14 +12,27 @@
  * <p>Testcontainers PostgreSQL 컨테이너를 띄우고, 애플리케이션 구동에 필요한
  * {@code mattermost.*} / {@code app.admin.*} 필수 프로퍼티를 더미 값으로 채워
  * 컨텍스트 리프레시가 실제 외부 시스템 없이도 성공하도록 한다.</p>
+ *
+ * <p><b>싱글턴 컨테이너 패턴.</b> 컨테이너는 테스트 JVM 전체에서 단 한 번만
+ * static 초기화 블록에서 직접 {@code start()} 하고, JUnit5의 {@code @Testcontainers}/
+ * {@code @Container} 확장은 의도적으로 쓰지 않는다. 그 확장을 쓰면 매 테스트 클래스가
+ * 끝날 때마다 컨테이너가 stop 되는데, Spring의 테스트 컨텍스트 캐시는 동일한 설정을
+ * 공유하는 다음 {@code AbstractDbTest} 하위 클래스에 대해 이미 만들어진
+ * {@code ApplicationContext}(및 그 안에서 죽은 포트를 가리키는 {@code DataSource})를
+ * 그대로 재사용해버려, 두 번째로 실행되는 DB 테스트 클래스가 "Connection refused"로
+ * 깨진다(전체 스위트에서만 재현되고 단독 실행 시엔 통과하는 이유). 여기서 컨테이너를
+ * 한 번만 띄우고 절대 {@code stop()} 하지 않으면(JVM 종료 시 Ryuk 사이드카가 정리)
+ * 모든 하위 테스트 클래스가 동일한 살아있는 컨테이너를 공유하므로 이 문제가 사라진다.</p>
  */
 @SpringBootTest
-@Testcontainers
 public abstract class AbstractDbTest {
 
-    @Container
     @ServiceConnection
-    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
+    static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
+
+    static {
+        postgres.start();
+    }
 
     @DynamicPropertySource
     static void secrets(DynamicPropertyRegistry registry) {
src/test/java/kr/itn/itnhub/SchemaMigrationTest.java
--- src/test/java/kr/itn/itnhub/SchemaMigrationTest.java
+++ src/test/java/kr/itn/itnhub/SchemaMigrationTest.java
@@ -2,35 +2,13 @@
 
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.PostgreSQLContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-@SpringBootTest
-@Testcontainers
-class SchemaMigrationTest {
-
-    @Container
-    @ServiceConnection
-    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
-
-    @DynamicPropertySource
-    static void secrets(DynamicPropertyRegistry registry) {
-        registry.add("app.admin.username", () -> "admin");
-        registry.add("app.admin.password", () -> "test-password");
-        registry.add("mattermost.base-url", () -> "http://localhost:1");
-        registry.add("mattermost.token", () -> "test-token");
-        registry.add("mattermost.team-id", () -> "test-team");
-    }
+class SchemaMigrationTest extends AbstractDbTest {
 
     @Autowired
     JdbcTemplate jdbc;
Add a comment
List