티스토리 뷰
코프링으로 개발 시 마주친 에러 관련 포스팅입니다.
개발 환경은 다음과 같습니다.
- Spring Boot Version : 3.0.1
- Java Version : 17
- Kotlin Version : 1.8.21
- Kotest Version : 5.5.5
다음 포스팅을 읽고 보시면 더욱 도움이 됩니다.
배경
위 포스팅을 간략하게 요약하자면 beforeSpec을 사용하여 sql script를 실행시켰고, 통합테스트를 진행했었습니다.
JUnit을 주로 사용해왔던 저로서는 당연히 DescirbeSpec 내 it절이 끝나면 beforeSpec에 넣어줬던 데이터들이 롤백되기를 기대했습니다.
하지만 롤백이 되지 않고, 해당 클래스 내 다른 테스트 케이스에도 영향을 끼쳤습니다.
심지어 build 시에 돌아가는 통합 테스트에서는 다른 클래스의 테스트에도 영향을 미쳤습니다..
단위 테스트는 성공 but, build 테스트는 실패
이게 제일 추적하기 힘들었습니다.
하나씩 돌리는 단위 테스트는 성공하나, 전체적으로는 실패했기 때문이죠.
로그는 하나 하나 찍어가며 실패한 이유를 찾아나선 과정을 소개 드리겠습니다.
[1]. afterSpec
처음 든 생각은 beforeSpec으로 데이터를 넣어줬으니, afterSpec으로 삭제해주면 되지 않을까 생각했습니다.
package com.laboratorykkoon9.kotlinspring.cafe.service
import com.laboratorykkoon9.kotlinspring.cafe.repository.CafeRepository
import com.laboratorykkoon9.kotlinspring.cafe.service.model.CreateCafeDto
import com.laboratorykkoon9.kotlinspring.cafe.service.model.UpdateCafeDto
import com.laboratorykkoon9.kotlinspring.config.QuerydslConfig
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.core.io.ClassPathResource
import org.springframework.data.domain.Pageable
import org.springframework.jdbc.datasource.init.ScriptUtils
import java.sql.SQLException
import javax.sql.DataSource
@DataJpaTest
@Import(QuerydslConfig::class)
@AutoConfigureTestDatabase(
replace = AutoConfigureTestDatabase.Replace.NONE,
)
internal class CafeServiceTest(
@Autowired private val cafeRepository: CafeRepository,
@Autowired private val dataSource: DataSource,
): DescribeSpec({
beforeSpec {
try {
dataSource.connection.use {conn ->
ScriptUtils.executeSqlScript(
conn,
ClassPathResource("/sql/cafe.sql")
)
}
} catch (e: SQLException) {
throw RuntimeException(e)
}
}
afterSpec {
cafeRepository.deleteAll()
}
val cafeService = CafeService(cafeRepository)
describe("getCafeInfo") {
context("모든 카페를 조회하면") {
val cafe = cafeService.getCafeInfo(Pageable.ofSize(1))
it("개수는 1개가 나온다.") {
cafe.content.size shouldBe 1
}
}
}
describe("createCafe") {
context("이미 존재하는 카페 이름으로 생성을 시도하면") {
val cafeDto = CreateCafeDto(
name = "둥겸의 커피집"
)
it("예외를 던진다.") {
shouldThrow<IllegalArgumentException> {
cafeService.createCafe(cafeDto)
}
}
}
}
})
하지만, 결과는 똑같이 build에서 실패가 났습니다..
[2]. beforeSpec에 truncate
dataSource로 실행한 스크립트이므로 삭제도 dataSource로 해야겠다고 생각이 들었습니다.
이게 원인이라면 내부적인 구조를 뜯어보고 다른 포스팅에서 자세히 다루겠습니다.
package com.laboratorykkoon9.kotlinspring.cafe.service
import com.laboratorykkoon9.kotlinspring.cafe.repository.CafeRepository
import com.laboratorykkoon9.kotlinspring.cafe.service.model.CreateCafeDto
import com.laboratorykkoon9.kotlinspring.cafe.service.model.UpdateCafeDto
import com.laboratorykkoon9.kotlinspring.config.QuerydslConfig
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.core.io.ClassPathResource
import org.springframework.data.domain.Pageable
import org.springframework.jdbc.datasource.init.ScriptUtils
import java.sql.SQLException
import javax.sql.DataSource
@DataJpaTest
@Import(QuerydslConfig::class)
@AutoConfigureTestDatabase(
replace = AutoConfigureTestDatabase.Replace.NONE,
)
internal class CafeServiceTest(
@Autowired private val cafeRepository: CafeRepository,
@Autowired private val dataSource: DataSource,
): DescribeSpec({
beforeSpec {
try {
dataSource.connection.use {conn ->
ScriptUtils.executeSqlScript(
conn,
ClassPathResource("/sql/truncate.sql")
)
}
dataSource.connection.use {conn ->
ScriptUtils.executeSqlScript(
conn,
ClassPathResource("/sql/cafe.sql")
)
}
} catch (e: SQLException) {
throw RuntimeException(e)
}
}
val cafeService = CafeService(cafeRepository)
describe("getCafeInfo") {
context("모든 카페를 조회하면") {
val cafe = cafeService.getCafeInfo(Pageable.ofSize(1))
it("개수는 1개가 나온다.") {
cafe.content.size shouldBe 1
}
}
}
describe("createCafe") {
context("이미 존재하는 카페 이름으로 생성을 시도하면") {
val cafeDto = CreateCafeDto(
name = "둥겸의 커피집"
)
it("예외를 던진다.") {
shouldThrow<IllegalArgumentException> {
cafeService.createCafe(cafeDto)
}
}
}
}
})
데이터를 넣어주기 전에 truncate로 깔끔하게 정리한 뒤, 데이터를 넣어주니 다른 테스트에 영향을 안 줄 수 있었습니다.
결론
아직 kotest와 dataSource가 내부적으로 어떻게 동작하는지 정확하게 알지 못해서 겪었던 오류라고 생각했습니다.
다음 포스팅에서는 beforeSpec에 사용되는 로직을 공통 함수로 추출하는 내용과 dataSource 및 kotest의 내부 동작에 대해서 자세하게 공부한 포스팅으로 찾아뵙겠습니다.
읽어주셔서 감사합니다~!
'개발 노트' 카테고리의 다른 글
[mac sonama] vscode code 명령어 에러 ./MacOS/Electron (0) | 2023.12.04 |
---|---|
깃허브(Github) Main 화면에 티스토리 최근 글 목록 자동으로 업데이트 해주기 (4) | 2023.11.23 |
[스프링+코틀린] API 버저닝 시 bean name conflict 해결 - BeanNameGenerator (0) | 2023.10.31 |
[스프링+코틀린] Optional 코틀린스럽게 구하기 - 엘비스 연산자 (2) | 2023.10.18 |
[스프링+코틀린] kotest와 @Sql 어노테이션 같이 사용 불가 - kotest에서 통합 테스트하기 (4) | 2023.10.02 |
- Total
- Today
- Yesterday
- Kotlin
- JPA
- kkoon9
- Spring Boot
- 코테
- 객체지향
- C++
- 클린 아키텍처
- Algorithm
- BOJ
- 프로그래머스
- Java
- 디자인 패턴
- Effective Java
- 이펙티브 자바
- MSA
- 이팩티브 자바
- 디자인패턴
- kotest
- 알고리즘
- 테라폼
- 클린 코드
- node.js
- Spring
- 백준
- programmers
- 정규표현식
- AWS
- BAEKJOON
- Olympiad
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |