■ 단일 DataSource 설정
properties/yaml에서 db connection 설정 방법은 이렇다.
spring.datasource.properties.driverClassName=...
spring.datasource.url=...
spring.datasource.username=...
spring.datasource.password=...
하지만 만약 멀티 데이터소스 요구사항이 다른 경우, 위와 같은 properties에 따른 기본적인 DataSource가 필요한 것이 아니라, 별도로 구현해둔 빈이 필요할 수 있다. (🔗 예를 들면 이런 상황)
그리고 그런 커스텀 DataSource를 사용하도록 테스트 환경을 구성할 때
어떻게 DataSource와 DB 접속 정보를 설정해야 하는지, 놓칠 수 있는 부분이 있어서 정리해둔다.
✔️ 결론적으로 test 환경에 대한 이해가 부족한 탓에 일어난 일
■ 테스트 DB 설정이 잘못되었을 때 발생 가능한 문제
❗이 문제가 바로 "테스트 환경과 DB 접속 세팅" 주제로 글을 정리한 이유다.
분명 내 DB 컨테이너에는 테이블이 잘 세팅되어 있었는데... 자꾸 SQLGrammarException: could not prepare statement [Table ~~ not found] 예외가 터졌다.
org.hibernate.exception.SQLGrammarException: could not prepare statement [Table "XXXXXXXX" not found (this database is empty); SQL statement:
~~~ sql문 ~~~]
꽤나 삽질을 했다. 처음에는 테이블명 대소문자 문제라고 생각했다. 그래서 DBMS에서 lower_case_table_names = 1 설정도 분명 잘 해뒀고, 컨테이너를 재부팅해도 마찬가지, 스프링부트 설정 내에서도 검색해서 나온 것들을 적용해봤는데 안되는 거 있지? 결국 테이블명 대소문자는 문제가 아니였다.
✔️ 원인: 테스트 환경에서 커스텀 DataSource가 나의 의도대로 정의되지 않은 상태였다. 그래서 설정파일(yaml, properties)에 적어둔 DB 접속 정보도 당연히 사용되지 않았고, 그냥 기본 DataSource가 Embedded H2 DB 기반으로 생성되는 상황이었다. 그래서 접속 정보가 없어도 DB connection 관련 예외 없이 잘 돌아갔으며.. 그저 Table이 없다는 예외만 뿜었을 뿐이다. 그래서 DB 접속 자체가 문제임을 뒤늦게 발견하게 되었다.
.
.
아마 동일한 문제 상황을 겪는다면, @SpringBootTest가 아닌 단위 테스트를 수행하려는 상황일 것이다. @SpringBootTest라면 커스텀 DataSource 빈이 잘 주입되었을 것이기 때문이다. 그러나 일반적인 단위테스트에서는 main 모듈에서 정의해둔 커스텀 DataSource를 @Import로 로드해줘야 한다.
▶▶ 해결책은 이정도로 충분하다.
.
.
아래 이어지는 글은 안 봐도 되는데.. 테스트 환경에서 커스텀 DataSource Bean을 어떻게 등록해야 했는지, 뭐가 문제였는지에 대한 과정을 좀 더 풀어쓴 내용이다.
Test 환경에서 DataSource 빈을 세팅하려는 상황을 나눠 생각해보자. 첫번째는 따로 정의가 필요없는 DataSource 빈을, 두번째는 내가 따로 정의한 DataSource 빈을 사용하려는 상황이다.
■ ① 기본 DataSource 빈
설정파일에 spring.datasource... 키를 통한 기본 db connection 설정울 했다면, 스프링부트의 Auto Configuration을 통해 설정한 내용대로 DataSource빈을 생성 & 주입할 수 있다는 것은 기본적으로 모두모두가 겪어보았을 것이다!
■ ② 커스텀 DataSource 빈
설정파일에 spring.datasource... 를 통한 기본 세팅 말고, 별도의 정의를 했고 커스텀 DataSource 빈을 정의해둔 상황이라면, 이를 Test 환경에 어떻게 적용할 수 있을까? 다양한 방법이 있을 것이다.
*일단 TestConfiguration에 해당 DataSource를 똑같이 등록하는 방법은 제외하겠다 (cause 코드 중복)
처음에는 Test 환경에서 별도의 테스트용 DataSource 빈 정의를 하지 않으면 main의 DataSource 빈을 사용하는 줄 알았다. 그러나 이것은 크나큰 오해라는 점... Import를 해야 한다!
✔️ @SpringBootTest가 아닌 한 main 빈 정의 내용이 Autowired 되지 않는다. 애초에 @SpringBootTest 유무를 명확히 구분짓고 있었더라면 실수할 일이 없을 것이다.
.
.
나는 @DataJpaTest를 하고 있다. 그리고 Embedded DB가 아닌 설정파일의 db connection 설정으로 DB접속을 할 거라서 @AutoConfigureTestDatabase도 아래처럼 설정해줬다. 이때 @DataJpaTest는 @SpringBootTest처럼 모든 빈을 로딩하는 것이 아니며 @Configuration또한 로딩 대상이 아니다.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE,
connection = EmbeddedDatabaseConnection.NONE)
@TestPropertySource(locations = "classpath:application-test.properties")
class SampleTests {
...
}
그래서 만약에 커스텀 DataSource 빈같은 @Configuration을 테스트 환경에서 사용하려면 Import해야 한다.
@Import(DataSourceConfig.class)
✔️ 즉 내가 허술했던 부분은 @AutoConfigureTestDatabase에 replace, connection에 NONE을 지정했으므로 properties에 설정한 db connection 정보를 사용하는데, 그에 따라 커스텀 DataSource가 잘 생성될 거라고 믿었던 것이다. 다시 올바르게 생각해보면, DataJpaTest는 Jpa 관련 빈(ex: repository)만 주입해주므로 Jpa와 무관한 빈을 주입해주는 의무는 없다.
■ Test 환경에서 / DataSource 관련 확인해본 내용
아래 설정을 유지하면서 몇가지 실험해봤다.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE,
connection = EmbeddedDatabaseConnection.NONE)
@TestPropertySource(locations = "classpath:application-test.properties")
class SampleTests {
...
}
설정사항을 다시 말해보자면 @DataJpaTest이므로 Jpa 관련 빈만 로드하며, DataJpaTest의 기본 설정인 h2 embedded db를 사용하는게 아니라 @AutoCongifureTestDatabase의 replace, connection 설정을 NONE, NONE으로 하여 properties에서 DB 접속 설정을 읽어 DataSource를 만들겠다는 설정이다. 그리고 그 properties는 test 모듈 아래 resources에다가 넣어뒀다.
*참고: @TestPropertiySource에 명시한 설정파일에서 값을 못 찾을 부분은, main의 application.properties에서 읽어오더라. 저렇게 명시한 파일'만' 고려하는 것은 아닌가보다.
*참고: build.gradle에 testImplementation 'com.h2database:h2'를 하지 않더라도, testImplementation 'org.springframework.boot:spring-boot-starter-test'를 해두면 내장 h2를 사용 가능하다. 스부 Testing 문서 41.1을 보면 Test scope dependencies를 안내하고 있는데 그 중에서 Spring Test가 들어가 있다. 그 Spring Test에 링크된 Spring Framework Reference Documentation 19.8에 아래 내용을 확인할 수 있음.
19.8 Embedded database support
The org.springframework.jdbc.datasource.embedded package provides support for embedded Java database engines. Support for HSQL, H2, and Derby is provided natively. You can also use an extensible API to plug in new embedded database types and DataSource implementations.
그럼 Test 환경에서 DataSource 관련해서 몇가지 얘기해보자..
.
.
[1] DataSource를 정의하지 않고, DB 기본 접속설정 X
⚠️@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE, connection = EmbeddedDatabaseConnection.NONE)로 명시했기 때문에 DB 접속정보를 properties에서 못 찾으면 예외가 터질 줄 알았는데, 그냥 기본 설정인 embedded h2 DB로 DataSource가 만들어졌다. 주의해야겠다.
[main][com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:565)] : {HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:4f8d5759-d60f-4cf1-b822-ac82704de8b3 user=SA}
[2] DataSource를 정의하지 않고, DB 기본 접속설정 O
즉 spring.datasource... 로 시작하는 단일 datasource 설정을 잘 해둔 경우인데,
기본 DataSource가 잘 만들어졌다.
여기부터 커스텀 DataSoure 설정 얘기.
[3] 커스텀 DataSource를 Import를 하지 않고, 커스텀 DB 접속설정 O
❌ main의 DataSource 빈 정의 내용은 테스트 환경에 로드되지 않으므로 properties에 커스텀 설정을 잘했는 못했든 상관 없다. 그저 기본 DataSource를 생성하기 위한 설정만 DataSource 생성에 고려될 뿐이다(spring.datasource.url, spring.datasource.driverClassName, spring.datasource.username, spring.datasource.password)
⚠️ 이 상황에서 내가 커넥션을 잘 맺었다고 착각했었다. 나는 분명 DataSource 빈도 main에 잘 써뒀고, 설정파일에 접속 정보도 잘 써뒀으니 DB 커넥션 문제는 없어! 라고 가정했었는데 가정 자체가 틀렸던 것. 사실 의도와 다르게 내장 H2에 접속했기에 커넥션 에러가 없었던 것.. 로그를 잘 봤으면 진작 찾았을 텐데..
[4] 커스텀 DataSource를 Import 했으며, 설정파일에 커스텀 접속정보 X
❌ 예외가 발생한다. 최상위에는 BeanCreationException: Error creating bean with name 'entityManagerFactory'가 터지는데, 결국 DataSource를 생성할 DB설정이 유효하지 않기 때문이다.
[5] 커스텀 DataSource를 Import 했으며, 설정파일에 커스텀 접속정보 O
⭕ 커스텀 DataSource를 사용하는 올바른 테스트 환경 구성이다.
■ Wrap up
커스텀 DataSource를 main에 정의했고, test에서 해당 빈을 사용하고 싶다면 @Import로 불러와야 한다. @SpringBootTest는 모든 빈을 로드해주기 때문에 DataSource도 잘 불러오겠으나, 그게 아니라면 @Import 해야하는 것이 당연하다.
.
.
단위 테스트를 하면서 빈의 주입 여부를 정립을 잘 못하고 되겠지 되겠지 했던게 실책... test 환경과 main 환경에 대한 이해가 부족했던 것, 그리고 test 환경에서는 main에서 정의된 빈을 당연히 자동으로 사용 가능한 것이 아닌데 스프링부트가 자동으로 해줄 거라고 생각했던 것을 반성해야겠다. 또한 @SpringBootTest, @DataJpaTest 개념을 가볍게 안다고 생각했는데 기본적인 테스트 DB 환경세팅을 할 때 DataSource 빈 주입에 대한 잘못된 생각을 하게된 걸 보아하니 한번 학습할 때 명확하게 해야겠다는 반성을 하며.. 그리고 로그를 좀 더 잘 검토하자는 다짐을 하면서.. 마무리한다 🤟
'JAVA > Application' 카테고리의 다른 글
MDC를 사용해 멀티쓰레드 로깅하기 (for 쓰레드간 Context 전달) (2) | 2024.12.16 |
---|---|
[Springboot] 스케줄러 동적 등록/삭제/변경 (2) | 2024.12.02 |
[Spring] DTO 매핑시 특정 키만 null 값 (2) | 2024.11.06 |
[Spring AOP] Consumer onMessage() 비동기 수신 로깅 (0) | 2024.10.31 |
[Redis] cannot deserialize from Object value (SerializationException) (0) | 2024.10.27 |