회사에서 많은 분들이 테스트를 @SpringBootTest로 사용하시고 H2를 안 쓰고 local 또는 dev 환경 데이터베이스에 붙어서 테스트를 진행하고 있습니다.
더 좋은 테스트 환경(빠른 테스트, 외부 환경에 의존하지 않는 테스트)을 위해 DataJpaTest와 TestContainer를 도입했는데 기록해 두면 좋을 거 같아 기록합니다~
목표
이번에 회사 코드에 DataJpaTest와 TestContainer를 도입하면서 목표는 다음과 같았습니다.
1. 사용하기 편할 것 (코드 한 줄 또는 두 줄로 바로 사용할 수 있게)
2. 테스트를 빠르게 실행시킬 것
이 두 가지 목표를 중요하게 생각한 이유는 다른 팀원들이 편하게 DataJpaTest와 TestContainer 세팅을 쓰면 좋겠다고 생각했고, 빠른 테스트 실행 시간이 보장되어야 개발 후 즉각적인 피드백을 받아 개발 생산성이 좋아진다고 생각했기 때문입니다.
DataJpaTest, TestContainer
먼저 DataJpaTest와 TestContainer에 대해서 알아봅시다.
Spring 공식 문서의 DataJpaTest 부분을 보면 다음과 같이 나와있습니다.
Testing Spring Boot Applications :: Spring Boot
To test whether Spring MVC controllers are working as expected, use the @WebMvcTest annotation. @WebMvcTest auto-configures the Spring MVC infrastructure and limits scanned beans to @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverte
docs.spring.io

번역 (GPT 선생님)
@DataJpaTest 애너테이션을 사용하여 JPA 애플리케이션을 테스트할 수 있습니다. 기본적으로, 이 애너테이션은 @Entity 클래스들을 스캔하고 Spring Data JPA 리포지토리를 구성합니다. 클래스패스에 임베디드 데이터베이스가 있으면, 그것도 자동으로 설정됩니다. SQL 쿼리는 spring.jpa.show-sql 속성을 true로 설정하여 기본적으로 로그에 기록되며, 이 기능은 애너테이션의 showSql 속성을 사용하여 비활성화할 수 있습니다.
@DataJpaTest 애너테이션을 사용할 때는 일반적인 @Component 및 @ConfigurationProperties 빈이 스캔되지 않습니다. @ConfigurationProperties 빈을 포함하려면 @EnableConfigurationProperties를 사용할 수 있습니다.
JPA Application을 구동하는 빈만 띄워 SpringBootTest보다 조금 더 경량화된 테스트를 할 수 있게 도와줍니다.
스프링 띄우는 시간을 조금이라도 줄이면 테스트가 빨라지겠죠??
이제 TestContainer에 대해 알아봅시다.
TestContainer의 공식 문서에 다음과 같이 나와있습니다.
Testcontainers for Java
Testcontainers for Java Not using Java? Here are other supported languages! About Testcontainers for Java Testcontainers for Java is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web brow
java.testcontainers.org

번역 (GPT 선생님)
Java용 Testcontainers는 JUnit 테스트를 지원하는 Java 라이브러리로, 가볍고 일회용으로 사용할 수 있는 데이터베이스, Selenium 웹 브라우저, 또는 Docker 컨테이너에서 실행될 수 있는 모든 것을 제공합니다.
Testcontainers는 다음과 같은 종류의 테스트를 더 쉽게 만들어 줍니다.
- 데이터 접근 계층 통합 테스트: MySQL, PostgreSQL, Oracle 등의 데이터베이스를 컨테이너화하여 사용함으로써, 개발자들의 로컬 환경에 복잡한 설정 없이도 데이터 접근 계층 코드를 완벽히 호환되도록 테스트할 수 있습니다. 또한, 테스트가 항상 알려진 데이터베이스 상태에서 시작된다는 점에서 안전합니다. 컨테이너화할 수 있는 다른 유형의 데이터베이스도 사용할 수 있습니다.
- 애플리케이션 통합 테스트: 데이터베이스, 메시지 큐 또는 웹 서버와 같은 종속성을 가진 애플리케이션을 단기간의 테스트 모드에서 실행할 수 있습니다.
- UI/인수 테스트: Selenium과 호환되는 컨테이너화된 웹 브라우저를 사용하여 자동화된 UI 테스트를 수행할 수 있습니다. 각 테스트는 브라우저 상태, 플러그인 변동 또는 자동 브라우저 업그레이드 걱정 없이 새 브라우저 인스턴스를 사용할 수 있습니다. 또한, 각 테스트 세션(또는 실패한 세션)에 대한 비디오 녹화도 제공합니다.
- 그 외에도 다양한 기능: 다양한 모듈을 확인하거나, GenericContainer를 기반으로 사용자 정의 컨테이너 클래스를 만들어 사용할 수 있습니다.
Java 코드를 바탕으로 도커 컨테이너를 테스트 환경에서 사용할 수 있게 해줍니다.
이렇게 테스트를 하면 개발자가 외부 환경을 세팅하지 않고 편하게 코드만 작성해 테스트를 진행할 수 있으면서 외부 환경과 격리된 테스트를 할 수 있게 됩니다.
DataJpaTest와 TestContainer의 더 자세한 내용은 링크로 달아둔 문서를 읽어주세요~~
build.gradle 세팅
회사 개발 환경이 Kotlin + SpringBoot + MySQL 환경임을 미리 알리며 세팅 방법을 공유합니다.
build.gradle.kts에 다음 의존성을 추가합니다.
testImplementation("org.testcontainers:mysql")
testImplementation("org.testcontainers:junit-jupiter")
testImplementation("org.testcontainers:testcontainers")
Singleton Container 설정하기
아주 쉽게 세팅하려면 다음과 같이 세팅할 수 있습니다.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class SampleTest {
@Autowired
private lateinit var sampleRepository: SampleRepository
companion object {
@JvmStatic
@Container
private val mySqlContainer = MySQLContainer(DockerImageName.parse("mysql:8.0.39"))
}
@BeforeEach
fun setup() {
sampleRepository.deleteAll()
}
}
다만 이런 방법으로 세팅하게 되면 DB 접근을 위한 테스트마다 해당 세팅을 다 넣어줘야 되는 불편함이 생깁니다. (쓰기 싫어짐)
그리고 해당 방식으로 테스트할 시 모든 테스트마다 컨테이너를 켰다 껐다 반복하기 때문에 테스트 소요 시간이 상당히 많이 소요됩니다. (테스트 실행하기 싫어짐)
이는 제가 목표로 했던 쉬운 세팅, 빠른 실행을 지킬 수 없는 방법이기 때문에 다른 방법을 찾아보았습니다.
여러 문서를 찾아본 결과 TestContainer 문서에서 Singleton Container를 활용하는 방법을 제공합니다.
Testcontainers container lifecycle management using JUnit 5
This guide will explain how to manage container lifecycle with Testcontainers using JUnit 5 lifecycle callbacks and JUnit 5 Extension annotations. We will also look into how to use Singleton Containers pattern to use same containers for multiple tests.
testcontainers.com
위 링크의 내용은 읽어보는 걸 추천합니다~~~
위 문서의 내용을 바탕으로 다시 세팅한 코드입니다.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(QuerydslConfig::class)
abstract class BaseMySQLContainerRepositoryTest {
companion object {
private val mySQLContainer = MySQLContainer(DockerImageName.parse("mysql:8.0.39"))
init {
mySQLContainer.start()
}
@JvmStatic
@BeforeAll
fun setup() {
System.setProperty("spring.datasource.url", mySQLContainer.jdbcUrl)
System.setProperty("spring.datasource.driver-class-name", mySQLContainer.driverClassName)
System.setProperty("spring.datasource.username", mySQLContainer.username)
System.setProperty("spring.datasource.password", mySQLContainer.password)
System.setProperty("spring.jpa.hibernate.ddl-auto", "update")
}
}
}
다른 분들은 setup 부분을 @DynamicPropertySource로 잘 세팅하는데 저는 잘 안되길래 System.setProperty()를 사용했습니다. 뭐가 문제인지 아신다면 알려주세요~~~~~~
이렇게 세팅을 하면 모든 테스트에 대해서 단 한 번 테스트 컨테이너가 실행되고 종료됨이 보장됩니다.
사용은 다음과 같이 편하게 사용할 수 있습니다.
internal class SampleTest: BaseMySQLContainerRepositoryTest() {
@Autowired
private lateinit var sampleRepository: SampleRepository
@Test
fun hello() {
println("hello")
}
}
클래스 하나만 상속받으면 된다!
추가) Logback 설정
테스트 컨테이너를 실행해 보면 로그가 엄청나게 뜹니다.
이를 지우기 위해 test의 resources 패키지에 아래 링크에 있는 Logback 설정을 해두면 좋습니다.
무조건 넣는 게 좋습니다.
Recommended logback configuration - Testcontainers for Java
Recommended logback configuration Testcontainers, and many of the libraries it uses, utilize SLF4J for logging. In order to see logs from Testcontainers, your project should include an SLF4J implementation (Logback is recommended). The following example lo
java.testcontainers.org
문제점 (코틀린만 해당됨)
이 방식으로 테스트를 하려면 Junit으로만 테스트해야 되는 문제가 있습니다. (Kotest에서 불가능)
회사 인턴이 Kotest로 하고 싶다고 해서 Kotest에서 DataJpaTest와 TestContainer를 사용하는 방법을 어찌저찌 구현해서 현재는 위 세팅을 쓰고 있지 않습니다.
Kotest 환경에서 DataJpaTest와 TestContainer를 사용하는 방법은 다음 포스팅으로 반드시 쓰도록 하겠습니다.
결론
공식 문서가 짱이다.
'Spring' 카테고리의 다른 글
| Spring에서 DB Read, Write 분기를 해보자 + 동작 방식도 알아보자 (0) | 2024.12.18 |
|---|---|
| Kotest에서 DataJpaTest와 TestContainer를 같이 써보자 (0) | 2024.08.30 |