나의 상황
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
class ThemeDaoTest {
private final JdbcTemplate jdbcTemplate;
private final ThemeDao themeDao;
private final SimpleJdbcInsert themeInsertActor;
@Autowired
public ThemeDaoTest(JdbcTemplate jdbcTemplate, ThemeDao themeDao) {
this.jdbcTemplate = jdbcTemplate;
this.themeDao = themeDao;
this.themeInsertActor = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("theme")
.usingGeneratedKeyColumns("id");
}
// ...
}
1. DirtiesContext의 classMode를 BEFORE_EACH_TEST_METHOD
로 설정한 경우
- 테스트 코드 내에서 jdbcTemplate를 사용하려고 하면 아래의 두 예외가 발생했다.
- JDBC connection을 가져올 수 없다.
- HikariPool-1이 닫혀있다. (HikariPool은 JDBC connection pool의 한 종류이다.)
2. DirtiesContext의 classMode를 AFTER_EACH_TEST_METHOD
로 설정한 경우
- 테스트 코드가 성공적으로 실행되었다.
왜지...?
@DirtiesContext
@DirtiesContext는 dirty한 ApplicationContext를 초기화하는 어노테이션이다.
클래스와 메서드 레벨에 붙을 수 있으며 언제 context를 재생성할지 지정할 수 있다.
Context 재생성 시기를 지정하는 여러 옵션 중 BEFORE_EACH_TEST_METHOD와 AFTER_EACH_TEST_METHOD의 경우 테스트가 어떤 순서로 동작되는지 확인해보았다.
1. BEFORE_EACH_TEST_METHOD
테스트 코드를 실행하기 전 스프링 서버를 한 번 열었다 닫는다.
로그의 14번째 라인까지 서버를 여는 로그이며 마지막 두 라인을 통해 서버를 닫음을 확인할 수 있다.
그 후 테스트 실행을 위해 스프링 서버를 한 번 더 띄운다. 이전(HikariPool-1)과는 다른 풀(HikariPool-2)이 할당됨을 확인할 수 있다.
결과적으로 BEFORE_EACH_TEST_METHOD 모드로 실행되는 로직은 아래와 같다.
[ 서버 ON - 서버 OFF ] - [ 서버 ON - 테스트 - 서버 OFF ] - [ 서버 ON - 테스트 - 서버 OFF ] - …
2. AFTER_EACH_TEST_METHOD
AFTER_EACH_TEST_METHOD 모드로 실행되는 로직은 아래와 같다.
[ 서버 ON - 테스트 - 서버 OFF ] - [ 서버 ON - 테스트 - 서버 OFF ] - …
HikariDataSource
다음으로는 두번째로 출력 되었던 아래의 에러 메시지를 분석해보자.
HikariDataSource 클래스를 살펴보았다.
HikariDataSource는 HikariPool을 가지고 있다.
스프링 서버를 올리고 내릴 때 콘솔에 출력된 로그 메시지를 참고하여 코드를 찬찬히 읽어보았다.
HikariDataSource HikariDataSource (HikariPool-1) has been closed.
예외 메시지가 이곳에서 출력됨을 알 수 있다.
스프링을 띄울 때 콘솔에 출력되던 위의 로그 또한 이곳에서 출력됨을 확인할 수 있다. 위 코드를 통해 스프링 서버를 띄우면서 HikariDataSource가 HikariPool을 할당받는다.
스프링 서버가 닫힐 때 출력되던 로그는 이곳에서 출력된다. 스프링 서버를 내릴 때 HikariPool을 shut down 함을 알 수 있다.
스프링 서버를 띄울 때 출력된 로그를 다시 확인해보자.
테스트 메서드를 실행할 때 띄워진 HikariPool의 이름은 HikariPool-2이었고, 예외의 원인이었던 HikariPool의 이름은 HikariPool-1이었다. HikariPool-1은 닫혀있고 현재 스프링 서버에서 열려있는 풀은 HikariPool-2인데, JdbcTemplate에 할당된 DataSource는 HikariPool-1에 존재한다. 즉 jdbcTemplate이, 닫혀 있는 HikariPool-1에 접근을 시도했으므로 예외가 발생한 것이다.
결론: @DirtiesContext 어노테이션을 통해 매번 스프링 서버가 재실행된다. 스프링 서버가 새로 시작될 땐 새로운 데이터베이스 풀이 할당되며, 스프링 서버를 종료할 때 사용한 풀을 닫는다.
번외) 생성자 주입 vs 필드 주입
의존성을 주입하는 방식에 따라서도 테스트 실행 결과가 달라졌다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
class ThemeDaoTest {
private final JdbcTemplate jdbcTemplate;
private final ThemeDao themeDao;
private final SimpleJdbcInsert themeInsertActor;
@Autowired
public ThemeDaoTest(JdbcTemplate jdbcTemplate, ThemeDao themeDao) {
this.jdbcTemplate = jdbcTemplate;
this.themeDao = themeDao;
this.themeInsertActor = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("theme")
.usingGeneratedKeyColumns("id");
}
// ...
}
생성자 주입 방식의 경우 단 한 번의 JdbcTemplate만을 할당받을 수 있기 때문에 처음 할당받은 객체를 그대로 사용해야한다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
class ThemeDaoTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ThemeDao themeDao;
private SimpleJdbcInsert themeInsertActor;
@Autowired
public ThemeDaoTest(JdbcTemplate jdbcTemplate, ThemeDao themeDao) {
this.jdbcTemplate = jdbcTemplate;
this.themeDao = themeDao;
this.themeInsertActor = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("theme")
.usingGeneratedKeyColumns("id");
}
// ...
}
필드 주입 방식의 경우 여러 번의 JdbcTemplate을 할당받을 수 있기 때문에 열려 있는 풀의 데이터 소스를 할당받을 것이다.
테스트 클래스가 생성될 때 생성자로 단 한 번의 의존성을 주입하는 것이 아닌, 필드를 통해 한 번 이상의 주입이 가능하면 예외 없이 성공적으로 동작한다. 필드 주입의 경우 매 테스트마다 새로 의존성을 주입받을 수 있기 때문이다.
테스트 결과
BEFORE_EACH_TEST_METHOD | 실패 | 불변인 JdbcTemplate에 할당된 풀이 닫혀 있다. | 성공 | 열려 있는 풀을 JdbcTemplate에 할당한다. |
AFTER_EACH_TEST_METHOD | 성공 | 불변인 JdbcTemplate에 할당된 풀이 열려 있다. | 성공 | 열려 있는 풀을 JdbcTemplate에 할당한다. |
참고
- https://www.baeldung.com/spring-dirtiescontext
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/annotation/DirtiesContext.html
근거는 있지만 저의 뇌피셜이므로 오류 보고 환영합니다!
'🌱 우아한테크코스 6기' 카테고리의 다른 글
[코리스토텔레스] 패키지 구조: 계층형 구조 vs 도메인형 구조 (0) | 2024.07.28 |
---|---|
[인프라] AWS EC2 (0) | 2024.07.22 |
[우아한테크코스] 2월 한달 회고 (32) | 2024.03.12 |
[GIT] cherry-pick | 다른 브랜치의 커밋 적용하기 (0) | 2024.03.04 |
[우아한테크코스] 6기 최종합격 회고 (3) | 2024.02.26 |