Kotestテストファクトリ(Test Factories)

ときには一般的なテストセットを作成し、特定の入力に再利用したい場合がある。Kotestでは、1つ以上のSpecに含められるテストを生成するテストファクトリを通じてこれを実現できる。

概要

独自のコレクションライブラリを作りたいと仮定しよう。やや単純な例だが、ドキュメント化の目的にはよく合う例である。

ListVectorという2つを実装するIndexedSeqインターフェースを作成できる。

interface IndexedSeq<T> {

    // returns the size of t
    fun size(): Int

    // returns a new seq with t added
    fun add(t: T): IndexedSeq<T>

    // returns true if this seq contains t
    fun contains(t: T): Boolean
}

List実装をテストしたい場合は、次のようにできる。

class ListTest : WordSpec({

   val empty = List<Int>()

   "List" should {
      "increase size as elements are added" {
         empty.size() shouldBe 0
         val plus1 = empty.add(1)
         plus1.size() shouldBe 1
         val plus2 = plus1.add(2)
         plus2.size() shouldBe 2
      }
      "contain an element after it is added" {
         empty.contains(1) shouldBe false
         empty.add(1).contains(1) shouldBe true
         empty.add(1).contains(2) shouldBe false
      }
   }
})

次にVectorをテストするには、テストをコピーして貼り付ける必要がある。さらに多くの実装とテストを追加すると、テストスイートが断片化し同期されなくなる可能性が高い。

この問題は、IndexedSeqをパラメータとして受け取るテストファクトリを作成することで解決できる。

テストファクトリを作成するには、funSpecwordSpecなどのビルダー関数を使う。ビルダー関数は各Specスタイルに対して存在する。

したがって、前のテストをテストファクトリへ変換するには次のようにする。

fun <T> indexedSeqTests(name: String, empty: IndexedSeq<T>) = wordSpec {
   name should {
      "increase size as elements are added" {
         empty.size() shouldBe 0
         val plus1 = empty.add(1)
         plus1.size() shouldBe 1
         val plus2 = plus1.add(2)
         plus2.size() shouldBe 2
      }
      "contain an element after it is added" {
         empty.contains(1) shouldBe false
         empty.add(1).contains(1) shouldBe true
         empty.add(1).contains(2) shouldBe false
      }
   }
}

その後、これを使うには1つのSpec、または複数のSpecに1回以上含める必要がある。

class IndexedSeqTestSuite : WordSpec({
   include(indexedSeqTests("vector"), Vector())
   include(indexedSeqTests("list"), List())
})

テストクラスには、通常のインラインテストだけでなく、複数種類のファクトリを含めることができる。例:

class HugeTestFile : FunSpec({

   test("first test") {
     // test here
   }

   include(factory1("foo"))
   include(factory2(1, 4))

   test("another test") {
     //  testhere
   }
})

含まれた各テストは、個別に定義されたかのようにテスト出力およびレポートに表示される。

Listeners

テストファクトリは、通常のテスト前後のコールバックをサポートする。ファクトリに追加されたすべてのコールバックは、そのファクトリが含まれるSpecへ順番に追加される。

ただし、コールバックはそのファクトリで生成されたテストにのみ適用される。つまり、独自のライフサイクルメソッドを持つ独立したファクトリを作成でき、他のファクトリやSpecに定義されたライフサイクルメソッドと衝突しないと確信できる。

例:

val factory1 = funSpec {
  beforeTest {
     println("Executing $it")
  }
  test("a") {  }
  test("b") {  }
}

class LifecycleExample : FunSpec({
   include(factory1)
   test("c")
   test("d")
})

テストスイートを実行すると、次の内容が出力される。

Executing a
Executing b

見て分かるように、factory1に追加されたbeforeTestブロックは、そのファクトリに定義されたテストにのみ適用され、追加されたSpecに定義されたテストには適用されない。


参照