우테코 레벨2 미션을 진행 중, SpringbootTest
를 하려는데 테스트 코드 예시에서 @DirtiesContext
를 마주쳤다. 처음 보는 애너테이션이었다. 이름만 보면 "Application Context를 초기화해주는구나!" 정도 알 수 있었다. 일단 한 번 써보았다.
테스트 실행 창을 보니 아래와 같이 매 테스트마다 1) "Initializing Spring embedded WebApplicationContext" 로그가 뜨면서 Application Context가 만들어진다. 또한 모든 테스트가 제대로 실행됐다. 2) DB가 매 테스트마다 초기화되었다. (나는 아무런 롤백 및 데이터 초기화를 해주지 않았다.) Springboot의 Application Context와 DB는 관계가 없는데.. 왜지?
우선 나는 위와 같이 H2를 사용하고 있었다.
@DirtiesContext
는 무슨 일을 하나요?Use this annotation if a test has modified the context — for example, by modifying the state of a singleton bean, modifying the state of an embedded database, etc. Subsequent tests that request the same context will be supplied a new context.
공식문서의 설명 세 줄로 의문이 해결되었다. (^^) embedded database의 상태를 변경한 것도 modified context로서 초기화된다. 표현이 이렇지 사실 매 테스트마다 ApplicationContext와 Tomcat을 재시작하는데, 어플리케이션이 종료되면 초기화되는 embedded db가 함께 초기화되는걸 이해하는 건 어렵지 않다.
내가 이해한게 맞나 싶어서 mysql로 테스트해보았다.
# application-mysql.properties
spring.datasource.url=jdbc:mysql://localhost:3306/roomescapeadmin?createDatabaseIfNotExist=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8
DB가 초기화되지 않아 저장된 데이터 개수를 조회하는 테스트가 통과되지 않았다. (이전에 실행되었던 다른 테스트 메소드 때문이다.)
아니다. 사실 @DirtiesContext
를 사용해서 DB가 초기화된 게 혼란스러웠던 이유는 이전에 H2를 Server mode로 사용해본 적이 있기 때문이다.
Embedded
헷갈릴 수 있겠지만, Spring에서 말하는 Embedded database와 다른 용어다. H2 문서에 기재된 Embedded mode는 로컬 파일에 db 데이터를 저장한다는 의미이다. 아래와 같이 url을 설정하고 프로젝트 디렉토리 내에 'data' 디렉토리를 만들면 그곳에 test라는 이름을 가진 파일(test.mv.db)이 생성된다.
# application-h2embedded.properties
spring.datasource.url=jdbc:h2:./data/test
추가 설정을 하지 않았다면 Spring에서 지원하는 Embedded database 기능을 사용할 수 없다. DB 초기화 스크립트* 라던가, @DirtiesContext
를 사용한 Embedded DB 초기화 같은 기능들 말이다. 역시 아래 테스트는 통과하지 않았다.
In-Memory
데이터를 영속화시킬 필요가 없을 때 사용한다. 이게 바로 Embedded database 기능을 하는 모드이다. 아래와 같이 url을 설정한다. 그 밖의 옵션은 H2 문서에서 확인할 수 있다.
# application.properties
spring.datasource.url=jdbc:h2:mem:database
어플리케이션이 종료되면 데이터가 날아간다. DB가 어플리케이션 메모리에 올라가기 때문이다. "rapid prototyping, testing, high performance operations, read-only databases" 등에서 사용하면 유용하다.
Server (using TCP)
H2는 세 종류의 서버를 지원한다. 1) a web server (for the H2 Console), 2) a TCP server (for client/server connections) and an 3) PG server (for PostgreSQL clients)가 있다. web server는 브라우저에 h2 console을 띄울 때 사용하는 것이고 Server mode에서 사용하는 건 TCP server이다. 이를 사용하려면 H2를 컴퓨터에 깔고 실행하는 과정이 선행되어야 한다.
위 사진(현재 돌아가는 프로세스 목록 중 일부)에서 보다싶이 H2 서버를 다른 프로세스로 실행하고 이를 springboot application이 연결하여 사용하는 것이다. mac을 기준으로 다운로드된 h2 폴더의 bin 디렉토리의 h2.sh
로 h2 서버를 실행하면 된다.
Embedded, In-Memory mode와 달리 동시에 여러 appliation들이 같은 DB에 접속할 수 있다. 아래와 같이 설정한다.
# application-h2server.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/Downloads/h2/bin/test
(/Downloads/h2/bin/test
이 경로는 내가 db file을 저장한 경로이다.)
당연히 Embedded DB가 아니므로 아래 테스트를 통과하지 못한다.
*DB 초기화 스크립트
DB 초기화 스크립트는 기본 경로가 /resources
이고 DDL은 schema.sql
로, DML은 data.sql
로 설정할 수 있다. 이 위치는 각각 spring.sql.init.schema-locations
와 spring.sql.init.data-locations
로 커스텀할 수 있다. 또한 중요한 점은, 기본값으로 SQL database initialization은 Embedded in-memory database에만 적용된다. spring.sql.init.mode
설정으로 always 혹은 never로 커스텀 가능하다.
Spring jdbc는 아래의 Embedded Database들을 지원한다.
직접 EmbeddedDatabaseBuilder
로 DataSource
를 생성해 Bean으로 등록하여 Embedded Database를 사용할 수 있다. 즉, 위에 언급했던 In-Memory 외에 Embedded나 Server mode도 Embedded Database로 사용할 수 있다. 물론 TCP connection을 사용하는 Server mode를 Embedded Database로 사용하는 건 '굳이' 같다.
Configuration
으로 아래와 같이 DataSource
를 등록한다.
@Configuration
public class DataSourceConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.build();
}
}
위처럼 Embedded Database를 설정했을 경우 Embedded mode여도 @DirtiesContext
가 제대로 동작한다.
@DirtiesContext
를 잘 사용했나요?아니요!
Embedded DB를 초기화할 수 있다는 기능 말고 SpringbootTest에서 캐싱되고 있는 ApplicationContext를 사용하지 않아야 하는 상황이 아니었다. 우선 Springboot의 Integration Test에서 Context Caching은 중요한 기능이다. Spring container에 의해 빈들을 찾고 등록하는 과정이 시간이 꽤나 걸리기 때문에 매 테스트마다 이 과정을 거치면 생산성이 떨어진다.
아래와 같이 ApplicationContext를 reload할 필요가 없어 @DirtiesContext
를 사용할 이유가 없었다. 싱글톤 빈들은 상태를 갖지 않게 했기 때문이다. (그래야만 하고)
되려 @DirtiesContext
를 사용해야만 하는 경우에 어떤 것들이 있는지 궁금해진다.
the unlikely case that a test corrupts the application context and requires reloading (for example, by modifying a bean definition or the state of an application object) the TestContext framework can be configured to reload the configuration and rebuild the application context before executing the next test.
참고로 DB 초기화, 롤백은 org.springframework.test.context.jdbc.Sql
애너테이션이나 org.springframework.transaction.annotation.Transactional
를 통해서도 구현할 수 있다.
로컬에서만 예쁘게 잘 돌아가는 프로그램을 만드는게 목표가 아니라면 외부 커넥션과 네트워크 관련된 부분을 잘 알아야 한다고 생각한다. 비록 H2는 인메모리로 테스트할 때 많이 사용하지만 디테일하게 공부하다 보면 DB 커넥션 등.. 과 같은 주제에 대해서 감이 잡히는 것 같다. 이것 저것 시도해보면서 많이 배웠다 👍
[1]
howto.data-initialization.using-basic-sql-scripts
[2]
springframework/test/annotation/DirtiesContext
[3]
jdbc/embedded-database-support
[4]
[5]
spring-framework/reference/testing/integration
공식문서를 해석하다 보니 제대로 이해한게 맞나 불안해서 직접 열심히 테스트하며 확인했다. 틀린 게 있다면 댓글로 알려주세요 🙏🏻