Web
수정·
Kotlin Kotest의 테스트 전/후 작업 지정
Kotlin Kotest의 전/후 작업 지정
Kotlin 진영에서 많이 사용하는 Kotest 에서는 테스트 전/후 필요한 작업을 수행할 수 있는 훅이 존재한다.
수행 시점별로 아래와 같다.
- beforeSpec
- beforeContainer
- beforeTest
- beforeEach
- afterSpec
- afterContainer
- afterTest
- afterEach
위 훅들을 필요 시점에 맞게 사용하여 테스트 전/후 Mock 객체 초기화 또는 객체 초기화 등을 수행하여 테스트 간 환경 격리를 할 수 있다.
문제는 용어들이 각 Spec 방식에서 사용하는 블력들의 명칭과 일치하지 않아 트리거 시점이 헷갈린다는 점이다.
BehaviorSpec
BehaviorSpec 은 도메인 중심적으로 테스트 내용을 서술하여 테스트를 작성하는 방식이다.
internal class TestBehaviorSpec() : BehaviorSpec {
private val requestValidator = mockk<RequestValidator>()
private val someService = SomeService(requestValidator)
init {
Given("올바른 요청이 주어진다") {
val request = SomeRequest()
every { requestValidator.validate(request) } returns true
When("요청을 수행하면") {
Then("성공한다") {
val result = someService.do(request)
result shouldBe true
}
}
}
}
}
위와 같은 방식으로 테스트를 작성하게 된다.
복잡한 테스트들의 경우 객체 생성 후 데이터를 조작하면 테스트 순서에 따라 테스트가 깨질 수 있고, 위 코드도 mock 을 clear하지 않아 다른 테스트에서 오작동할 위험이 있다. 이를 위해 각 테스트 별로 mock을 초기화 하는 등의 작업이 필요하다.
가령 위 코드는 아래 코드를 추가하여 mock 누적을 방지할 수 있다.
```kotlin
internal class TestBehaviorSpec() : BehaviorSpec {
private val requestValidator = mockk<RequestValidator>()
private val someService = SomeService(requestValidator)
init {
...
afterEach {
clearAllMocks()
}
}
}
Spec, Container, Test, Each
테스트 격리는 단순히 각 테스트 이후 mock 객체를 초기화 하는 것보다 복잡한 작업이 요구될 때가 많다. 가령 테스트마다 매번 어떤 객체의 상태를 변경하는 어떤 함수를 호출하고 그 결과를 검증해야하는 경우 상태 변화가 누적되기 때문에 객체의 상태를 필요한 시점에 초기화 하는 작업이 필요하다. 여기서는 각 용어가 어떤 영역에서 트리거 되는지 확인할 필요가 있다.
- Spec 테스트 클래스 내의 모든 테스트들에 대해 딱 한번 수행된다. 내부에 2개의 Given, 각각 2개의 When/Then 으로 구성되어 4개의 테스트가 존재하더라도 단 한번만 수행된다.
internal class TestBehaviorSpec() : BehaviorSpec {
private val requestValidator = mockk<RequestValidator>()
private val someService = SomeService(requestValidator)
init {
...
beforeSpec {
println("Called beforeSpec!")
}
...
}
}
# 출력 예시
Called beforeSpec!
- Container Given내에 작성하며 Given 내에서 전후 작업 통제에 이용된다.
internal class TestBehaviorSpec() : BehaviorSpec {
private val memberRepository = mockk<MemberRepository>()
private val memberService = MemberService(memberRepository)
private var member: Member = Member()
init {
...
beforeSpec {
println("Called beforeSpec!")
}
Given("회원이 존재한다") {
beforeContainer {
member = createMember()
every { memberRepository.find(any()) } returns member
}
...
}
}
}
- Test 각 테스트 블록 내 바인딩 될 위치에 작성하고, 그 스코프 내에서 필요한 작업을 작성한다.
internal class TestBehaviorSpec() : BehaviorSpec {
private val memberRepository = mockk<MemberRepository>()
private val memberService = MemberService(memberRepository)
private var member: Member = Member()
init {
...
beforeSpec {
println("Called beforeSpec!")
}
Given("회원이 존재한다") {
beforeContainer {
member = createMember()
every { memberRepository.find(any()) } returns member
}
When("회원이 로그인을 하면") {
beforeTest {
every { member.login(password=any()) } returns success
}
...
}
...
}
}
}
- Each 모든 테스트 케이스별로 전후 실행된다.