Kotest 統計(Statistics)

ジェネレーターが期待どおりに構成されているかを確認するために、Kotest で生成される値の種類を知りたい場合がある。

統計(Statistics)

ジェネレーターが望んだ方法で構成されているかを確認するために、Kotest で生成される値の種類を知りたい場合がある。プロパティテスト統計は、このような要求を満たすために設計されている。

collect 関数は統計の開始点であり、値のカテゴリを数えるために使用される。この関数は、プロパティテスト内で増やしたいカテゴリを指定して呼び出す。

たとえば、BigDecimal が使用する RoundingMode 値の統計を収集したいとする。通常どおり checkAll を呼び出し、丸めモードを collect 関数に渡す。

checkAll(Arb.enum<RoundingMode>(), Arb.bigDecimal()) { mode, decimal ->
  collect(mode)
  // test here
}

テストが完了すると、Kotest はテスト名と各カテゴリの件数/割合を出力する。

Statistics: [collecting stats] (1000 iterations, 1 args)

HALF_DOWN                                                     142 (14%)
HALF_UP                                                       141 (14%)
CEILING                                                       132 (13%)
FLOOR                                                         122 (12%)
UP                                                            119 (12%)
UNNECESSARY                                                   119 (12%)
HALF_EVEN                                                     118 (12%)
DOWN                                                          107 (11%)

使用するカテゴリは enum である必要はない。任意のオブジェクトにでき、より細かく制御したい場合は条件付きでラップできる。

checkAll(Arb.int()) { k ->
  when {
    k % 2 == 0 -> collect("EVEN")
    else -> collect("ODD")
  }
  // test here
}

ラベル(Labels)

直交する統計セットが必要になることがある。たとえば簡単な数値テストでは、特定の割合が偶数であり、特定の割合が負数であることを確認したい場合がある。1 つの方法は EVEN_POSEVEN_NEGODD_POSODD_NEG を使うことである。

checkAll(Arb.int()) { k ->
  when {
    k > 0 && k % 2 == 0 -> collect("EVEN_POS")
    k % 2 == 0 -> collect("EVEN_NEG")
    k > 0 -> collect("ODD_POS")
    else -> collect("ODD_NEG")
  }
  // test here
}

これにより、1 つの出力セットが得られる。

EVEN_POS                                                       142 (27%)
EVEN_NEG                                                       141 (23%)
ODD_POS                                                        132 (24%)
ODD_NEG                                                        122 (26%)

しかし、組み合わせが増えると扱いにくくなるため、Kotest はラベル付き統計をサポートする。これは別々の統計セットと考えられる。ラベルを使用するには、collect メソッドの最初の引数にラベル名を渡すだけでよい。

checkAll(Arb.int()) { k ->
  when {
    k % 2 == 0 -> collect("even_odd", "EVEN")
    else -> collect("even_odd", "ODD")
  }
  when {
    k > 0 -> collect("pos_neg", "POS")
    else -> collect("pos_neg", "NEG")
  }
  // test here
}

これで Kotest は、タイトルにラベル名を含む複数の統計セットを出力する。

Statistics: [collecting labelled stats] (1000 iterations, 1 args) [even_odd]

ODD                                                           520 (52%)
EVEN                                                          480 (48%)


Statistics: [collecting labelled stats] (1000 iterations, 1 args) [pos_neg]

NEG                                                           527 (53%)
POS                                                           473 (47%)

レポートモード

デフォルトでは、すべてのプロパティテストの統計が出力される。グローバル構成オブジェクト PropertyTesting で構成できる 4 つのモードがある。

可能なオプションは次のとおりである。

Mode Function
PropertyTesting.statisticsReportMode = StatisticsReportMode.OFF すべての統計レポートを無効にする。
PropertyTesting.statisticsReportMode = StatisticsReportMode.ALL すべての統計レポートを有効にする。
PropertyTesting.statisticsReportMode = StatisticsReportMode.SUCCESS 成功したテストでのみ統計を出力する。
PropertyTesting.statisticsReportMode = StatisticsReportMode.FAILED 失敗したテストでのみ統計を出力する。

統計のカバレッジ確認

特定の値が生成されていることをプログラムで主張するには、満たす必要がある制約を指定できる。

たとえば、前の丸めの例で withCoveragePercentages を使用し、少なくとも 10% の入力が HALF_DOWN をカバーし、10% が FLOOR をカバーすることを確認できる。

withCoveragePercentages(mapOf(RoundingMode.HALF_DOWN to 10.0, RoundingMode.FLOOR to 10.0)) {
  checkAll(Arb.enum<RoundingMode>(), Arb.bigDecimal()) { mode, decimal ->
    collect(mode)
    // use the mode / decimal
  }
}

割合ではなく絶対数で確認するには、withCoverageCounts を使用する。

withCoverageCounts(mapOf(RoundingMode.HALF_DOWN to 75, RoundingMode.FLOOR to 75)) {
  checkAll(Arb.enum<RoundingMode>(), Arb.bigDecimal()) { mode, decimal ->
    collect(mode)
    // use the mode / decimal
  }
}

カスタムレポート

統計レポーターの独自インスタンスを使用して、レポート形式をカスタマイズしたり、生データからレポートを生成したりできる。これはグローバル構成オブジェクト PropertyTesting を通じて構成される。

例:

object MyStatisticsReporter : object : StatisticsReporter { ... }
PropertyTesting.statisticsReporter = MyStatisticsReporter

参照