Kotestのテストスタイル(Testing Styles)

Kotestはさまざまなテストスタイル(Testing Styles)を提供している。ここでは、各テストスタイルについて説明する。

テストスタイル

Kotestはさまざまなテストスタイル(Testing Styles)を提供している。ここでは、Kotestがサポートする10種類のテストスタイルを紹介する。

Testing Style 影響を受けたもの
Fun Spec ScalaTest
String Spec A Kotest original
Should Spec A Kotest original
Describe Spec Javascript frameworks and RSpec
Behavior Spec BDD frameworks
Word Spec ScalaTest
Free Spec ScalaTest
Feature Spec Cucumber
Expect Spec A Kotest original
Annotation Spec JUnit

それぞれのスタイルは特定の構文に従い、状況に応じて選択して使用できる。各テストスタイルにはそれぞれの特徴と長所、短所があるため、プロジェクトの要件に合わせて適切なスタイルを選択できる。

以下では各テストスタイルについて説明し、基本的な使い方を、いずれかのスタイルを継承するサンプルコードで確認する。

FunSpec

FunSpecは関数ベースのテストスタイルである。各テストケースを関数として作成し、テスト本体をその関数内に記述する。

FunSpecを使用すると、テストを説明する文字列引数を指定してtest関数を呼び出し、テスト自体をラムダとして渡すことでテストを作成できる。どのスタイルを使うべきか迷う場合は、このスタイルを使うのがよい。

以下はFunSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

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

class FunSpecTest : FunSpec({
    test("FunSpecの例 - 加算テスト") {
        val result = 2 + 2
        result shouldBe 4
    }
})

通常の方法に加えて、xcontextおよびxtestのバリエーションを使用してテストを無効にできる。

class MyTests : FunSpec({
    context("this outer block is enabled") {
        xtest("this test is disabled") {
            // test here
        }
    }
    xcontext("this block is disabled") {
        test("disabled by inheritance from the parent") {
            // test here
        }
    }
})

StringSpec

StringSpecは文字列ベースのテストスタイルである。自然言語に近い構文でテストを記述できる。主に各テストケースを文字列として記述し、テスト本体を波括弧{}内に記述する。

StringSpecは構文を最小限に抑える。テストコードでは、文字列の後にラムダ式を書くだけでよい。

以下はStringSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

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

class StringSpecTest : StringSpec({
    "StringSpecの例 - 加算テスト" {
        val result = 2 + 2
        result shouldBe 4
    }
})

テストに設定を追加することもできる。

class MyTests : StringSpec({
    "strings.length should return size of string".config(enabled = false, invocations = 3) {
        "hello".length shouldBe 5
    }
})

ShouldSpec

ShouldSpecFunSpecに似ているが、testの代わりにshouldキーワードを使って記述する。主にテストケースの動作を仕様として表現するために使用される。

以下はShouldSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.ShouldSpec
import io.kotest.matchers.shouldBe

class ShouldSpecTest : ShouldSpec({
    should("ShouldSpecの例 - 加算テスト") {
        val result = 2 + 2
        result shouldBe 4
    }
})

テストは1つ以上のコンテキストブロックにネストすることもできる。

class MyTests : ShouldSpec({
    context("String.length") {
        should("return the length of the string") {
            "sammy".length shouldBe 5
            "".length shouldBe 0
        }
    }
})

通常の方法に加えて、xcontextおよびxshouldのバリエーションを使用してテストを無効にできる。

class MyTests : ShouldSpec({
    context("this outer block is enabled") {
        xshould("this test is disabled") {
            // test here
        }
    }
    xcontext("this block is disabled") {
        should("disabled by inheritance from the parent") {
            // test here
        }
    }
})

DescribeSpec

DescribeSpecテストスタイルはdescribe / itキーワードを使用するため、RubyやJavaScriptの背景を持つ人にとってなじみやすいスタイルである。describe構文を使用してテストケースをグループ化し、テストは1つ以上の説明ブロックにネストする必要がある。主にテストケースを説明するために使用される。

以下はDescribeSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe

class DescribeSpecTest : DescribeSpec({
    describe("DescribeSpecの例") {
        it("加算テスト") {
            val result = 2 + 2
            result shouldBe 4
        }
    }
})

通常の方法に加えて、xdescribeおよびxitのバリエーションを使用してテストを無効にできる。

class MyTests : DescribeSpec({
    describe("this outer block is enabled") {
        xit("this test is disabled") {
            // test here
        }
    }
    xdescribe("this block is disabled") {
        it("disabled by inheritance from the parent") {
            // test here
        }
    }
})

BehaviorSpec

振る舞い駆動開発(BDD, Behavior Driven Development)スタイルの書き方である。 givenwhenthenブロックを使用してテストを記述する。 主要な機能や動作に焦点を当ててテストケースを作成する。

以下はBehaviorSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class BehaviorSpecTest : BehaviorSpec({
    Given("BehaviorSpecの例") {
        When("加算テスト") {
            val result = 2 + 2
            Then("2と2を足すと4になるべきである") {
                result shouldBe 4
            }
        }
    }
})

GivenWhenAndキーワードを使用して深さを追加することもできる。

class MyTests : BehaviorSpec({
    given("a broomstick") {
        and("a witch") {
            `when`("The witch sits on it") {
                and("she laughs hysterically") {
                    then("She should be able to fly") {
                        // test code
                    }
                }
            }
        }
    }
})

通常の方法に加えて、xgivenxwhenxthenのバリエーションを使用してテストを無効にできる。

class MyTests : BehaviorSpec({
    xgiven("this is disabled") {
        When("disabled by inheritance from the parent") {
            then("disabled by inheritance from its grandparent") {
                // disabled test
            }
        }
    }
    given("this is active") {
        When("this is active too") {
            xthen("this is disabled") {
               // disabled test
            }
        }
    }
})

WordSpec

WordSpecは文字列ベースのテストスタイルである。自然言語に近い構文を使用してテストを記述する。 shouldキーワードを使用して、コンテキスト文字列の後にテストをネストする。各テストケースを文字列として記述し、テスト本体をshouldブロック内に記述する。

以下はWordSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe

class WordSpecTest : WordSpec({
    "WordSpecの例 - 加算テスト" should {
        "2と2を足すと4になるべきである" {
            val result = 2 + 2
            result shouldBe 4
        }
    }
})

また、別のネストレベルを追加できるようにwhenキーワードもサポートしている。whenはKotlinのキーワードであるため、バッククォートまたは大文字のバリエーションを使用する必要がある点に注意する。

class MyTests : WordSpec({
    "Hello" When {
        "asked for length" should {
            "return 5" {
                "Hello".length shouldBe 5
            }
        }
        "appended to Bob" should {
            "return Hello Bob" {
                "Hello " + "Bob" shouldBe "Hello Bob"
            }
        }
    }
})

FreeSpec

FreeSpecは自由形式のテストスタイルである。テストケース間の依存性を減らし、独立してテストを記述できる。

FreeSpecを使用すると、外側のテストでは-(マイナス)キーワードを使用し、最終テストではテスト名だけを使用して、任意の深さでネストできる。

以下はFreeSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe

class FreeSpecTest : FreeSpec({
    "FreeSpecの例 - 加算テスト" - {
        val result = 2 + 2
        "2と2を足すと4になるべきである" {
            result shouldBe 4
        }
    }
})

FeatureSpec

FeatureSpecは機能(Feature)ベースのテストスタイルである。各テストケースを機能またはシナリオに合わせて作成する。

FeatureSpecを使用すると、機能とシナリオを使用できる。これはCucumberを使ったことがある人にはなじみがあるだろう。Cucumberと完全に同じではないが、キーワードはそのスタイルを模倣している。

以下はFeatureSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.FeatureSpec
import io.kotest.matchers.shouldBe

class FeatureSpecTest : FeatureSpec({
    feature("FeatureSpecの例") {
        scenario("加算テスト") {
            val result = 2 + 2
            result shouldBe 4
        }
    }
})

通常の方法に加えて、xfeatureおよびxscenarioのバリエーションを使用してテストを無効にできる。

class MyTests : FeatureSpec({
    feature("this outer block is enabled") {
        xscenario("this test is disabled") {
            // test here
        }
    }
    xfeature("this block is disabled") {
        scenario("disabled by inheritance from the parent") {
            // test here
        }
    }
})

ExpectSpec

テスト仕様をexpect構文を使用して記述する。主に期待結果を明示するために使用される。

ExpectSpecFunSpecShouldSpecに似ているが、expectキーワードを使用する。

以下はExpectSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.ExpectSpec
import io.kotest.matchers.shouldBe

class ExpectSpecTest : ExpectSpec({
    context("ExpectSpecの例") {
        expect("加算テスト") {
            val result = 2 + 2
            2 + 2 shouldBe 4
        }
    }
})

テストは1つ以上のコンテキストブロックにネストすることもできる。

class MyTests : ExpectSpec({
    context("a calculator") {
        expect("simple addition") {
            // test here
        }
        expect("integer overflow") {
            // test here
        }
    }
})

通常の方法に加えて、xcontextおよびxexpectのバリエーションを使用してテストを無効にできる。

class MyTests : ExpectSpec({
    context("this outer block is enabled") {
        xexpect("this test is disabled") {
            // test here
        }
    }
    xcontext("this block is disabled") {
        expect("disabled by inheritance from the parent") {
            // test here
        }
    }
})

AnnotationSpec

アノテーションベースのテストスタイルである。特定のアノテーションを使用してテストを記述する。

JUnitから移行する場合、AnnotationSpecはJUnit 4/5のようなアノテーションを使用するSpecである。Specクラスに定義した関数に@Testアノテーションを追加するだけでよい。

JUnitと同様に、before tests/specsafter tests/specsにアノテーションを追加できる。

@BeforeAll / @BeforeClass
@BeforeEach / @Before
@AfterAll / @AfterClass
@AfterEach / @After

テストを無視するには@Ignoreを使用する。

以下はAnnotationSpecのサンプルコードである。

package com.devkuma.kotest.tutorial.testingstyles

import io.kotest.core.spec.style.AnnotationSpec
import io.kotest.matchers.shouldBe

class AnnotationSpecExample : AnnotationSpec() {

    @BeforeEach
    fun beforeTest() {
        println("Before each test")
    }

    @Test
    fun test1() {
        1 shouldBe 1
    }

    @Test
    fun test2() {
        3 shouldBe 3
    }
}

テストの構造化

Kotestでは、テストを構造化するためにcontextブロックを使用し、テスト階層またはグループとしてテストケースを作成できる。これにより、テストを論理的に分類し、関連するテストケースをまとめて管理できる。

例えば、次はテストグループを使用してテストを構造化する例である。

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

class ContextTest : FunSpec({

    context("Calculator tests") {
        test("Addition") {
            // Test logic for addition
        }

        test("Subtraction") {
            // Test logic for subtraction
        }
    }

    context("String tests") {
        test("Length") {
            // Test logic for string length
        }

        test("Concatenation") {
            // Test logic for string concatenation
        }
    }
})

上の例では、contextブロックを使用してテストを分類して分けている。

contextブロックはFunSpecShouldSpecExpectSpecでのみ使用できる。


参考