Kotest プロパティテスト関数(Property Test Functions)

Kotest でプロパティテストを実行するために使用される 2 つの関数バリエーションは forAllcheckAll である。

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 関数に渡していることがわかる。

組み込みジェネレーターの一覧はこちらを参照する。


参照