Kotest 테스트 팩토리(Test Factories)
개요
자체 컬렉션 라이브러리를 만들고 싶다고 가정해 보자. 다소 진부한 예이지만 문서화 목적에 잘 부합하는 예이다.
List
과 Vector
라는 두 가지를 구현할 IndexedSeq
인터페이스를 만들 수 있다.
interface IndexedSeq<T> {
// returns the size of t
fun size(): Int
// returns a new seq with t added
fun add(t: T): IndexedSeq<T>
// returns true if this seq contains t
fun contains(t: T): Boolean
}
List
구현을 테스트하고 싶다면 이렇게 하면 된다:
class ListTest : WordSpec({
val empty = List<Int>()
"List" should {
"increase size as elements are added" {
empty.size() shouldBe 0
val plus1 = empty.add(1)
plus1.size() shouldBe 1
val plus2 = plus1.add(2)
plus2.size() shouldBe 2
}
"contain an element after it is added" {
empty.contains(1) shouldBe false
empty.add(1).contains(1) shouldBe true
empty.add(1).contains(2) shouldBe false
}
}
})
이제 Vector
를 테스트하려면 테스트를 복사하여 붙여넣어야 한다. 더 많은 구현과 더 많은 테스트를 추가하면 테스트 스위트가 파편화되고 동기화되지 않을 가능성이 높다.
이 문제는 IndexedSeq
을 매개변수로 받아들이는 테스트 팩토리를 생성하여 해결할 수 있다.
테스트 팩토리를 생성하기 위해 funSpec
, wordSpec
등과 같은 빌더 함수를 사용한다. 빌더 함수는 각 스펙 스타일에 대해 존재한다.
따라서 이전 테스트를 테스트 팩토리로 변환하려면 다음을 수행하면 된다:
fun <T> indexedSeqTests(name: String, empty: IndexedSeq<T>) = wordSpec {
name should {
"increase size as elements are added" {
empty.size() shouldBe 0
val plus1 = empty.add(1)
plus1.size() shouldBe 1
val plus2 = plus1.add(2)
plus2.size() shouldBe 2
}
"contain an element after it is added" {
empty.contains(1) shouldBe false
empty.add(1).contains(1) shouldBe true
empty.add(1).contains(2) shouldBe false
}
}
}
그런 다음 이를 사용하려면 이를 하나의 스펙(또는 여러 스펙)에 한 번 이상 포함해야 한다.
class IndexedSeqTestSuite : WordSpec({
include(indexedSeqTests("vector"), Vector())
include(indexedSeqTests("list"), List())
})
TIP
모든 스타일 팩토리를 모든 스타일 스펙에 포함할 수 있다. 예를 들어 재미있는 스펙 팩토리를 문자열 스펙 클래스에 포함할 수 있다.테스트 클래스에는 일반적인 인라인 테스트뿐만 아니라 여러 가지 유형의 팩토리가 포함될 수 있다. 예를 들어:
class HugeTestFile : FunSpec({
test("first test") {
// test here
}
include(factory1("foo"))
include(factory2(1, 4))
test("another test") {
// testhere
}
})
포함된 각 테스트는 개별적으로 정의된 것처럼 테스트 출력 및 보고서에 나타낸다.
NOTE
팩토리의 테스트는 스펙 클래스에 정의된 순서대로 포함된다.Listeners
테스트 팩토리는 일반적인 테스트 전후의 콜백을 지원한다. 팩토리에 추가된 모든 콜백은 해당 팩토리가 포함된 스펙에 차례로 추가된다.
단, 해당 팩토리에서 생성된 테스트에만 콜백이 적용된다. 즉, 자체 수명 주기 메서드가 있는 독립형 팩토리를 만들 수 있으며 다른 팩토리 또는 스펙에 정의된 수명 주기 메서드와 충돌하지 않는다는 확신을 가질 수 있다.
예를 들어:
val factory1 = funSpec {
beforeTest {
println("Executing $it")
}
test("a") { }
test("b") { }
}
class LifecycleExample : FunSpec({
include(factory1)
test("c")
test("d")
})
테스트 스위트를 실행하면 다음과 같은 내용이 출력된다:
Executing a
Executing b
보시다시피, factory1
에 추가된 beforeTest
블록은 해당 팩토리에 정의된 테스트에만 적용되며, 추가된 스펙에 정의된 테스트에는 적용되지 않는다.