Kotest 프로퍼티 테스트 함수(Property Test Functions)
forAll
과 checkAll
두가지가 있다.For All
첫 번째로 forAll
은 프로퍼티(Property)를 테스트하는 n-arity 함수 (a, ..., n) -> Boolean
을 받는다. 모든 입력 값에 대해 함수가 참을 반환하면 테스트가 통과된다.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.forAll
class PropertyExample: StringSpec({
"String size" {
forAll<String, String> { a, b ->
(a + b).length == a.length + b.length
}
}
})
이 함수는 인자 유형에 대한 유형 매개변수를 허용하며, 최대 14개의 아리티(arity)를 지원한다. Kotest는 이러한 유형 매개변수를 사용하여 적절한 유형의 임의 값을 제공하는(생성하는) 제너레이터를 찾는다.
예를 들어, forAll<String, Int, Boolean> { a, b, c -> }
는 인자 a
가 임의의 문자열, 인자 b
가 임의의 int, 인자 c가 임의의 부울인 3-arity 프로퍼티 테스트이다.
Check All
두 번째로 checkAll
은 입력에 대해 간단히 단언문을 실행할 수 있는 n-arity 함수(a, ..., n) -> Unit
을 받는다. 이 접근 방식은 예외가 발생하지 않으면 테스트가 유효한 것으로 간주한다. 다음은 동일한 예제를 체크올을 사용하여 동일한 방식으로 다시 작성한 것이다.
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.string.shouldHaveLength
import io.kotest.property.checkAll
class PropertyExample: StringSpec({
"String size" {
checkAll<String, String> { a, b ->
a + b shouldHaveLength a.length + b.length
}
}
})
두 번째 접근 방식은 부울을 반환하는 것보다 더 일반적인 목적이지만, 첫 번째 접근 방식은 이 라이브러리에 영감을 준 원래 하스켈 라이브러리에서 유래한 것이다.
Iterations
기본적으로 Kotest는 프로퍼티 테스트를 1000회 실행한다. 테스트 메서드를 호출할 때 반복 횟수를 지정하여 이를 쉽게 사용자 지정할 수 있다.
테스트를 10,000회 실행한다고 가정해 보겠다.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.checkAll
class PropertyExample: StringSpec({
"a many iterations test" {
checkAll<Double, Double>(10_000) { a, b ->
// test here
}
}
})
제너레이터 지정하기
앞 예제에서 Kotest가 유형 매개변수에 따라 자동으로 값을 제공하는 것을 보았다. 필요한 유형에 대한 값을 생성하는 제너레이터를 찾아서 이를 수행한다. 예를 들어, 자동으로 제공되는 정수 제너레이터는 음수, 양수, 무한대, 0 등 가능한 모든 값에서 임의의 정수를 생성한다.
이는 기본적인 테스트에 적합하지만 샘플 공간에 대한 더 많은 제어를 원하는 경우가 많다. 예를 들어, 특정 범위의 숫자에 대해서만 함수를 테스트하고 싶을 수 있다. 그런 경우 제너레이터를 수동으로 지정해야 한다.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.forAll
class PropertyExample4 : StringSpec({
fun isDrinkingAge(a: Int): Boolean =
a in 19..Int.MAX_VALUE
"is allowed to drink in Chicago" {
forAll(Arb.int(21..150)) { a ->
isDrinkingAge(a) // assuming some function that calculates if we're old enough to drink
}
}
"is allowed to drink in London" {
forAll(Arb.int(18..150)) { a ->
isDrinkingAge(a) // assuming some function that calculates if we're old enough to drink
}
}
})
두 개의 테스트를 생성하고 각 테스트에서 적절한 int 범위를 가진 forAll
함수에 제너레이터를 전달한 것을 볼 수 있다.
내장된 제너레이터 목록은 여기를 참조하라.