Kotest プロパティテスト関数(Property Test Functions)
forAll と checkAll である。For All
まず forAll は、プロパティ(Property)をテストする n-arity 関数 (a, ..., n) -> Boolean を受け取る。すべての入力値に対して関数が true を返すと、テストは成功する。
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 がランダムな boolean である 3-arity プロパティテストである。
Check All
次に checkAll は、入力に対して単純にアサーションを実行できる n-arity 関数 (a, ..., n) -> Unit を受け取る。このアプローチでは、例外が発生しなければテストは有効と見なされる。次は同じ例を checkAll を使って同じ方法で書き直したものである。
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
}
}
})
2 つ目のアプローチは boolean を返すよりも汎用的だが、1 つ目のアプローチはこのライブラリに影響を与えた元の Haskell ライブラリに由来する。
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
}
}
})
2 つのテストが作成され、それぞれのテストで適切な int 範囲を持つジェネレーターを forAll 関数に渡していることがわかる。
組み込みジェネレーターの一覧はこちらを参照する。