반응형
@DataJpaTest를 어떻게 작성하는지에 대한 기록
JPA 테스트를 위한 의존성 구성
dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.mysql:mysql-connector-j")
// test container를 위한 의존성 라이브러리
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.testcontainers:mysql:1.17.6")
}
@SpringBootTest를 사용하지 않고, @DataJpaTest를 사용하는 이유
- @SpringBootdTest의 경우 테스트를 띄울 때, ApplicationContext에 Bean들을 등록하기 때문에 로딩 시간이 오래 걸린다.
- 반면에, @DataJpaTest의 경우 JPA 테스트에 필요한 Configuration들만 불러오기 때문에 테스트 시간 절약에 도움이 된다.
- 통합/인수 테스트 환경에서는 임베디드 환경으로 구성할 필요가 있겠지만, Repository 테스트는 격리된 환경에서 진행되어야한다고 생각하여 @DataJpaTest를 통해 테스트 환경을 구축하는 것이 맞다고 생각한다.
@TestInstance
- 인자값으로 @TestInstance(TestInstance.Lifecycle.PER_CLASS)로 설정
- 기본적으로 테스트 코드는 메서드마다 테스트 환경이 재구성되는데, 데이터베이스를 메서드마다 띄우게되면 많은 시간을 소모하게 된다.
@ActiveProfiles
- 테스트를 위한 설정 파일인 application-test.yml을 만든다
spring:
test:
database:
replace: none # 내장 DB가 아닌, Docker 를 띄우므로 다른 테스트에서도 Container 를 공유할 수 있다
datasource:
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url: jdbc:tc:mysql:8:///
jpa:
hibernate:
ddl-auto: update # 생성&드랍으로 하게 되면 혹시 모를 개발, 운영 DB에 영향을 미칠 수 있다.
JPA 테스트 클래스 사전 구성
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles("test")
@DataJpaTest
class JpaTest {
@PersistenceContext
private lateinit var entityManager: EntityManager
@Autowired
private lateinit var repository: DashboardListDao
private final val MYSQL_IMAGE = "mysql:8"
protected val MYSQL_CONTAINER = MySQLContainer(MYSQL_IMAGE)
@BeforeAll
fun before() {
MYSQL_CONTAINER.start()
}
@AfterAll
fun after() {
MYSQL_CONTAINER.stop()
}
...
}
@PersistenceContext란 무엇이고, 왜 사용하는가?
- EntityManger를 Thread-Safe하게 다루기 위한 복잡한 코드를 AOP로 구현한 것
- EntityManger 그 자체로는 thread-safe하지 않아서 싱글톤을 통해 기본 빈 관리를 하는 스프링에서는 예상치 못한 결과가 나올 수 있다
- thread-safe한 EntityManagerFactory를 빈으로 생성하고, Entity를 사용할 때마다 factory.cereateEntityManager()를 통해 EntityManager를 Proxy로 생성하는 방식으로 사용
- type을 EXTENDED로 주면 스레드 간에 영속성 컨텍스트를 공유하게 된다. (기본으로 TRANSACTION)
- 예전에는 @Autowired로 설정할 경우 Spring에 의해 싱글톤으로 관리되어 thread-safe하지 않았지만, 최근에는 @PersistenceConext와 동일하게 동작한다.
@Autowired & @PersistenceContext와 @PersistenceContext(type = PersistenceContextType.EXTEND) 사용
@Autowired
private lateinit var entityManager_autowired: EntityManager
@Test
fun ENTITY_MANAGER를_AUTOWIRED로_선언한다() {
println("autowired => $entityManager_autowired")
}
@PersistenceContext(type = PersistenceContextType.EXTENDED)
private lateinit var entityManager_extend: EntityManager
@PersistenceContext
private lateinit var entityManager_transaction: EntityManager
@Test
fun ENTITY_MANAGER를_ENTENDED로_선언한다() {
println("EXTENDED => $entityManager_extend")
}
@Test
fun ENTITY_MANAGER를_TRANSACTION으로_선언한다() {
println("TRANSACTION => $entityManager_transaction")
}
// 결과 값
autowired => Shared EntityManager proxy for target factory [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@70ab1ce0]
TRANSACTION => Shared EntityManager proxy for target factory [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@70ab1ce0]
EXTENDED => SessionImpl(1799080631<open>)
전체 코드
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ActiveProfiles("test")
@DataJpaTest
class JpaTest {
@Autowired
private lateinit var entityManager_autowired: EntityManager
@PersistenceContext
private lateinit var entityManager_transaction: EntityManager
@PersistenceContext(type = PersistenceContextType.EXTENDED)
private lateinit var entityManager_extend: EntityManager
private final val MYSQL_IMAGE = "mysql:8"
protected val MYSQL_CONTAINER = MySQLContainer(MYSQL_IMAGE)
@BeforeAll
fun before() {
MYSQL_CONTAINER.start()
}
@AfterAll
fun after() {
MYSQL_CONTAINER.stop()
}
@Test
fun ENTITY_MANAGER를_AUTOWIRED로_선언한다() {
println("autowired => $entityManager_autowired")
}
@Test
fun ENTITY_MANAGER를_ENTENDED로_선언한다() {
println("EXTENDED => $entityManager_extend")
}
@Test
fun ENTITY_MANAGER를_TRANSACTION으로_선언한다() {
println("TRANSACTION => $entityManager_transaction")
}
}
반응형
LIST
'Spring Framework > JPA' 카테고리의 다른 글
JPA findAll()은 어떻게 쿼리 결과를 객체로 파싱할까? (feat. 메서드 호출 과정) (0) | 2023.11.17 |
---|---|
Hibernate 6.2 업데이트 시, Kotlin @OneToMany 제네릭 타입 이슈 (0) | 2023.10.24 |
[Spring Boot JPA] JPA란 무엇일까? (0) | 2020.05.12 |
JPA - 환경설정(feat. 스프링 부트) (0) | 2019.08.21 |