1. 概要

Kotest は、Kotlinで記述されたマルチプラットフォームのテストフレームワークです。 これは、3つの主要なサブプロジェクトで構成されています。

  • テストフレームワーク
  • アサーションライブラリ
  • 特性試験

各プロジェクトは、他のテストフレームワークから独立して使用できます。 たとえば、assertjアサーションでJupiterの代わりにKotestフレームワークを使用することができます。

Kotestテストは、JVM、Javascript、またはネイティブで実行できます。 これにより、バックエンド、モバイル、およびWeb開発に同じテストライブラリを使用できます。

このチュートリアルでは、JVMプラットフォームでのみテストを実行することに焦点を当てます。

2. JVMでのテスト

Kotestは、JVMでJUnitプラットフォームを使用します。 したがって、 Maven プロジェクトでは、次の宣言を使用してアクティブ化できます。

<dependency>
    <groupId>io.kotest</groupId>
    <artifactId>kotest-runner-junit5-jvm</artifactId>
    <version>5.1.0</version>
    <scope>test</scope>
</dependency>

3. テストスタイル

Kotestは、さまざまなテストスタイルを提供します。 いくつかの人気のあるスタイルの例を見てみましょう。

3.1. 動作仕様

give when then キーワードを使用して、このスタイルでBDDのようなテストを記述できます。

class CardPaymentTests : BehaviorSpec({
    given("I have sufficient balance") {
        `when`("I make a card payment") {
            then("The card payment should be successful") {
                // test code
            }
        }
    }
})

3.2. スペックする必要があります

shouldキーワードを使用してテストを作成できます。

class MoneyTests : ShouldSpec({
    should("Convert input money to the target currency") {
        // test code
    }
})

関連するテストをcontextブロックにグループ化できます。

class PaymentTests : ShouldSpec({
    context("CardPayments") {
        should("Make a card payment") {
            // test code
        }
    }
    context("BankTransfers") {
        should("Make an external bank transfer") {
            // test code
        }
    }
})

3.3. 機能仕様

次に、featureおよびscenarioキーワードを使用して、エンドツーエンドのcucumberのようなテストを作成する方法を見てみましょう。

class HomePageTests : FeatureSpec({
    feature("signup") {
        scenario("should allow user to signup with email") {
            // test code
        }
    }
    feature("signin") {
        scenario("should allow user with valid credentials to login") {
            // test code
        }
    }
})

3.4. 仕様を説明する

describe、を使用して、JavascriptおよびRuby開発者の間で非常に人気のあるスタイルでテストを作成できます。

class PaymentTests : DescribeSpec({
    describe("CardPayments") {
        it("Should make a card payment") {
            // test code
        }
    }
    describe("BankTransfers") {
        it("Should make an external bank transfer") {
            // test code
        }
    }
})

4. アサーション

Kotestには、アサーション用の個別のライブラリがあることを以前に見てきました。 これらのライブラリは、テストで流暢なアサーションを作成するためのいくつかのmatcher関数を提供します。 アサーションライブラリには大きく分けて2つのカテゴリがあります。

  • コアマッチャー
  • 外部マッチャー

kotest-assertions-coreライブラリのマッチャーの例をいくつか見てみましょう。

// verify actual object is equal to expected object
result.shouldBe(expected)

// verify actual expression is true
result.shouldBeTrue()

// verify actual object is of given type
result.shouldBeTypeOf<Double>()

// verify actual map contains the given key
result.shouldContainKey(key)

// verify actual map contains the given values
result.shouldContainValues(values)

// verify actual string contains the given substring
result.shouldContain("substring")

// verify actual string is equal to the given string ignoring case
result.shouldBeEqualIgnoringCase(otherString)

// verify actual file should have the given size
result.shouldHaveFileSize(size)

// verify actual date is after the given date
result.shouldBeBefore(otherDate)

コアアサーションモジュールに加えて、JSONマッチャー、JDBCマッチなど、さまざまなシナリオのマッチャーを提供するモジュールがいくつかあります。

5. 例外のテスト

一方、Kotestを使用した例外のテストは非常に簡単です。

val exception = shouldThrow<ValidationException> {
   // test code
}
exception.message should startWith("Invalid input")

6. ライフサイクルフック

ライフサイクルフックを使用して、テストの前後にテストフィクスチャをセットアップまたは分解できます。 これらのフックは、Junitのセットアップおよびティアダウンの方法と非常によく似ています。 例を見てみましょう:

class TransactionStatementSpec : ShouldSpec({
    beforeTest {
      // add transactions
    }
    afterTest { (test, result) ->
      // delete transactions
    }
})

7. データ駆動型テスト

Kotestのデータ駆動型テストは、Junit5パラメーター化テストに似ています。 異なる入力データで複数のテストを作成する代わりに、単一のテストケースに複数の入力を提供して、さまざまな例を確認できます。 kotest-framework-datatest-jvmライブラリのuseData関数を使用して、テストにデータを提供できます。

例を見てみましょう:

data class TaxTestData(val income: Long, val taxClass: TaxClass, val expectedTaxAmount: Long)

class IncomeTaxTests : FunSpec({
    withData(
      TaxTestData(1000, ONE, 300),
      TaxTestData(1000, TWO, 350),
      TaxTestData(1000, THREE, 200)
    ) { (income, taxClass, expectedTaxAmount) ->
        calculateTax(income, taxClass) shouldBe expectedTaxAmount
    }
})

8. 非決定論的テスト

場合によっては、結果を同期的に返さない関数をテストする必要があります。 残念ながら、コールバック関数やスレッドでのスリープなどの手法を使用して結果を待機するカスタムボイラープレートコードを作成する必要があるため、このような関数をテストするのは難しいです。

Kotestは、このような非決定論的テストを宣言型で記述するために使用できるいくつかの便利な関数を提供します。

最終的に関数の例を見てみましょう。

class TransactionTests : ShouldSpec({
    val transactionRepo = TransactionRepo()

    should("Should make transaction complete") {
        eventually({
            duration = 5000
            interval = FixedInterval(1000)
        }) {
            transactionRepo.getStatus(120) shouldBe "COMPLETE"
        }
    }
})

ここで、テストでは最大5秒間、トランザクションのステータスを1秒ごとにチェックします。

9. 嘲笑

mockkなどの任意のモッキングライブラリをKotestと統合できます。 Kotestは、独自のモックライブラリを提供していません。

class ExchangeServiceTest : FunSpec({
    val exchangeRateProvider = mockk<ExchangeRateProvider>()
    val service = ExchangeService(exchangeRateProvider)

    test("Exchanges money using rate from exchange rate service") {
        every { exchangeRateProvider.rate("USDEUR") } returns 0.9
        service.exchange(Money(1200, "USD"), "EUR") shouldBe 1080
    }
})

10. テストカバレッジ

Jacoco をKotestと統合して、テストカバレッジを測定できます。 統合するには、単体テストの実行後にテストカバレッジレポートが生成されるようにする必要があります。

tasks.test {
    finalizedBy(tasks.jacocoTestReport)
}

テストカバレッジのHTMLレポートは、 $ buildDir / reports / jacoco /testディレクトリ内にあります。

11. タグを使用したテストのグループ化

特定の環境で特定のテストのみを実行したい場合があります。 たとえば、git pull request検証ビルドの一部として、いくつかの遅いテストを実行することを避けたい場合があります。 そのためには、最初にテストにタグを付ける必要があります。

@Tags(NamedTag("SlowTest"))
class SlowTests : ShouldSpec({})

12. 結論

このチュートリアルでは、Kotestフレームワークによって提供されるいくつかの基本的な機能について学習しました。

いつものように、コード例はGitHubにあります。