Kotestタグを使用したテストのグループ化(Grouping Tests with Tags)

効果的なソフトウェアテストには、テストケースを作成し管理することが重要である。このセクションでは、Kotestを使ってテストケースを作成する方法について見ていく。

Tagの生成と定義

すべてのテストを実行したくない場合がある。この場合、Kotestは実行時にどのテストを実行するかを決められるタグを提供する。

Tagを継承して生成する

タグはio.kotest.core.Tagクラスを継承するオブジェクトである。

たとえば、オペレーティングシステム別にテストをグループ化するには、次のタグを定義できる。

import io.kotest.core.Tag

class Windows : Tag()
class Linux : Tag()

NamedTagクラスを使用して定義する

また、NamedTagクラスを使ってタグを定義することもできる。

たとえば、次のようにする。

val linux = NamedTag("Linux")

このNamedTagクラスを使用する場合は、次の規則に従う必要がある。

  • タグはnullまたは空白であってはならない。
  • タグに空白を含めてはならない。
  • タグにISO制御文字を含めてはならない。
  • タグに次の文字を含めてはならない。
    • !: 感嘆符
    • (: 左丸括弧
    • ): 右丸括弧
    • &: アンパサンド
    • |: パイプ

テストのマーキング

生成したTagでconfig関数を使い、テストケースにタグを付けられる。

import io.kotest.core.spec.style.StringSpec

class TagConfigTest : StringSpec() {
    init {
        "should run on Windows".config(tags = setOf(Windows)) {
            // ...
        }

        "should run on Linux".config(tags = setOf(Linux)) {
            // ...
        }

        "should run on Windows and Linux".config(tags = setOf(Windows, Linux)) {
            // ...
        }
    }
}

タグを使用して実行する

システムプロパティkotest.tagsでテストランナーを呼び出し、どのテストが実行されるかを制御することもできる。渡す式は、ブール演算子(&|!)を使用する単純なブール式である。括弧でまとめて連結することもできる。

例:

Tag1 & (Tag2 | Tag3)

テストを実行するときは、タグオブジェクトの単純名、つまりパッケージを除いた名前を入力する。大文字と小文字の使用に注意する必要がある。2つのタグオブジェクトの単純名が同じ場合、たとえ別の名前空間にあっても同じタグとして扱われる。

たとえば、Linuxでタグ付けされたテストだけを実行し、Databaseタグ付きのテストは実行したくない場合、Gradleを次のように呼び出す。

gradle test -Dkotest.tags="Linux & !Database"

このようにシステムプロパティ(kotest.tags)を入れると、LinuxでありDatabaseではない場合だけ実行される。

タグは実行時にも含めたり除外したりできる。たとえば、プロパティの代わりにプロジェクト構成を実行する場合は、RuntimeTagExpressionExtensionを通じて行う。

RuntimeTagExpressionExtension.expression = "Linux & !Database"

タグ式演算子

演算子(優先順位の降順)

演算子 意味 使用例
! not !macos
& and linux & intergration
| or windows

すべてのテストにタグを指定する

Spec自体のtags関数を使うと、Spec内のすべてのテストにタグを追加することもできる。

たとえば、次のようにtags関数でタグを指定できる。

import io.kotest.core.spec.style.FunSpec

class TagFuncTest : FunSpec({

    tags(Linux, MySql)

    test("my test") { } // automatically marked with the above tags
})

Specにタグを指定する

Specクラスに追加できるアノテーションは@Tags@RequiresTagの2つであり、1つ以上のタグ名を引数として使用できる。

@Tags

1つ目のタグである@Tagsアノテーションは、クラス内のすべてのテストに適用される。これは、特定のタグが明示的に除外されてテストが実行されない場合に、そのSpecがインスタンス化されないことを意味する。

Tagsを使用する基本的な方法は次のとおりである。

たとえば、一部の遅いテストを実行したくない場合がある。

import io.kotest.core.spec.style.FreeSpec
import io.kotest.core.annotation.Tag

@Tags("fast")
class MyFastTests : FreeSpec({
    "テスト 1" {
        // テスト内容
    }
})

@Tags("slow")
class MySlowTests : FreeSpec({
    "テスト 2" {
        // テスト内容
    }
})

テストでkotest.tags=fastを指定すると、fastで指定されたテストだけが実行される。

複数のタグを一緒に使って、特定条件に合うテストを実行できる。

@Tags("fast", "smoke")
class MyFastSmokeTests : FreeSpec({
    "テスト 3" {
        // テスト内容
    }
})

上のコードは、fastsmokeの2つのタグがどちらも指定されたテストを表す。この場合、kotest.tagsシステムを使って、2つのタグのうち1つまたは両方を考慮して実行できる。

次は、@Tagsconfigtags関数でタグを指定した場合に、システムプロパティ(kotest.tags)の命令がどのように動作するかを示す例である。

@Tags("Linux")
class MyTestClass : FunSpec({

  tags(UnitTest)

  beforeSpec { println("Before") }

  test("A").config(tags = setOf(Mysql)) {}
  test("B").config(tags = setOf(Postgres)) {}
  test("C") {}
})
ランタイムタグ Spec生成 コールバック呼び出し 結果
kotest.tags=Linux Y Y すべてのテストがアノテーションからLinuxタグを継承するため、A、B、Cが実行される。
kotest.tags=Linux & Mysql Y Y すべてのテストにLinuxタグがあるが、AだけがMysqlタグを持つため、Aだけが実行される。
kotest.tags=!Linux N N テストは実行されず、タグアノテーションにより除外できるためMyTestClassはインスタンス化されない。
kotest.tags=!UnitTest Y N すべてのテストがtags関数からUnitTestを継承するため、テストは実行されない。
MyTestClassはクラスに定義されたタグを検索するためにインスタンス化される。有効なテストがないためbeforeSpecコールバックは実行されない。
kotest.tags=Mysql Y Y Mysqlでマークされた唯一のテストであるため、Aだけが実行される。
kotest.tags=!Mysql Y Y B、Cだけが実行される。AはMysqlでマークされて除外されたためである。
kotest.tags=Linux & !Mysql Y Y すべてのテストがアノテーションからLinuxを継承するが、AはMysqlタグによって除外されるため、B、Cだけが実行される。

@RequiresTag

2つ目のタグである@RequiresTagは、参照されたタグがすべて存在するかどうかだけを確認し、存在しない場合はSpecをスキップする。

たとえば、次のSpecは、実行時にLinuxおよびMysqlタグが指定されていない場合、スキップされてインスタンス化されない。

@RequiresTag("Linux", "Mysql")
class MyTestClass : FunSpec()

タグ継承

デフォルトでは、@Tagsアノテーションはそのアノテーションが適用された直前のSpecでのみ考慮される。しかし、Specはスーパークラスおよびスーパーインターフェースからタグを継承することもできる。これを有効にするには、プロジェクト構成でtagInheritance = trueに切り替える。

Gradleシステムプロパティを渡すための修正

Gradle構成には特別な注意が必要である。

システムプロパティ(-Dx=y)を使用するには、テスト実行へ伝播されるようGradleを構成し、テストに追加構成を加える必要がある。

Groovy:

test {
    //... Other configurations ...
    systemProperties = System.properties
}

Kotlin Gradle DSL:

val test by tasks.getting(Test::class) {
    // ... Other configurations ...
    systemProperties = System.getProperties().asIterable().associate { it.key.toString() to it.value }
}

または、次のように-Dkotest.tags=<tags>だけを受け取って渡せるように設定することもできる。

val test by tasks.getting(Test::class) {
    // ... Other configurations ...
    systemProperties.putAll(System.getProperties().filter { it.key == "kotest.tags" }.map { it.key.toString() to it.value.toString() }.toMap())
}

これにより、JVMでシステムプロパティを正しく読み取れる。


Tagは、特定の環境や条件に応じて特定のテストを選択的に実行または除外するときに便利であり、テストをより効率的に管理し構成できるようにする。

参照