Kotest 축소(Shrinking)
축소(Shrinking)
속성 기반 테스트에서 처음에 발견된 실패 사례에는 실제로 테스트를 실패하게 만드는 많은 복잡성이 포함되어 있을 수 있다. 축소란 속성 기반 테스트 프레임워크가 실패 사례를 단순화하여 재현 가능한 최소한의 사례를 찾아내는 메커니즘이다. Kotest에서 제너레이터에서 실패 사례를 축소하는 방식은 축소 인터페이스의 구현에 의해 정의된다. 기본 제공 제너레이터에는 일반적으로 프레임워크에 의해 정의된 기본 축소기가 있으며, 사용자 지정 제너레이터에는 사용자 지정 축소기 구현을 지정할 수 있다.
기본 제공 제너레이터의 축소
기본 제공 제너레이터(제너레이터 목록 참조)에는 프레임워크에서 정의한 기본 축소 함수가 있다. 축소 함수는 테스트에 실패한 값을 입력으로 받아 Kotest가 테스트를 적용할 수 있는 새 값의 목록을 반환한다. 정확한 동작은 데이터 유형에 따라 다르다. 예를 들어 문자열의 경우 첫 번째 또는 마지막 문자를 삭제하여 축소할 수 있고 정수의 경우 값을 줄이거나 절반으로 줄일 수 있다. 또한 빈 문자열이나 정수 0과 같은 에지 케이스에 대해 축소 동작이 정의되어 있다. 이러한 제너레이터를 사용하는 테스트가 실패할 때 축소가 수행된다.
Arb.positiveInt().checkAll { i ->
calculateProperty(i) shouldBe true
}
생성된 입력 중 하나에 대해 테스트가 실패하면 축소 결과가 표시된다:
Property test failed for inputs
0) 1792716902
Caused by io.kotest.assertions.AssertionFailedError: expected:<1792716902> but was:<0> at
PropertyBasedTest$1$1$3$1.invokeSuspend(PropertyBasedTest.kt:54)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
io.kotest.property.internal.ProptestKt$proptest$3$2.invokeSuspend(proptest.kt:45)
Attempting to shrink arg 1792716902
Shrink #1: 1 pass
Shrink #2: 597572300 fail
Shrink #3: 199190766 fail
Shrink #4: 66396922 fail
Shrink #5: 22132307 fail
Shrink #6: 7377435 fail
Shrink #7: 2459145 fail
[...]
Shrink #999: 29948 pass
Shrink #1000: 44922 pass
Shrink #1001: 59896 pass
Shrink #1002: 89839 fail
Shrink result (after 1002 shrinks) => 89839
Caused by io.kotest.assertions.AssertionFailedError: expected:<89839> but was:<0> at
PropertyBasedTest$1$1$3$1.invokeSuspend(PropertyBasedTest.kt:54)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
PropertyBasedTest$1$1$3$1.invoke(PropertyBasedTest.kt)
io.kotest.property.internal.ShrinkfnsKt$shrinkfn$1$1$smallestA$1.invokeSuspend(shrinkfns.kt:19)
기본적으로 Kotest는 1000배 축소된다. 이 동작은 구성할 수 있다. 예를 들어 제한 없이 계속 축소하려는 경우:
Arb.positiveInt().checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Unbounded)) { i ->
calculateProperty(i) shouldBe true
}
사용자 지정 제너레이터의 축소
사용자 지정 제너레이터에는 Kotest에서 정의한 축소기가 없다. 대신 사용자 지정 수축기를 구현할 수 있다. 아래는 축소기가 값 자체 옆에 있는 좌표를 반환하는 예제이다.
data class Coordinate(val x: Int, val y: Int)
class CoordinateTest : FunSpec({
context("Coordinate Transformations") {
// Shrinker takes the four neighbouring coordinates
val coordinateShrinker = Shrinker<Coordinate> { c ->
listOf(
Coordinate(c.x - 1, c.y),
Coordinate(c.x, c.y - 1),
Coordinate(c.x + 1, c.y),
Coordinate(c.x, c.y + 1),
)
}
val coordinateArb = arbitrary(coordinateShrinker) {
Coordinate(Arb.nonNegativeInt().bind(), Arb.nonNegativeInt().bind())
}
test("Coordinates are always positive after transformation") {
coordinateArb.checkAll {
transform(it).x shouldBeGreaterThanOrEqualTo 0
transform(it).y shouldBeGreaterThanOrEqualTo 0
}
}
}
})