1. 序章

仕様テストフレームワークは、アプリケーションをテストするためのユニットテストフレームワークを補完します

このチュートリアルでは、 Spekフレームワーク–JavaおよびKotlinの仕様テストフレームワークを紹介します。

2. 仕様テストとは何ですか?

簡単に言えば、仕様テストでは、仕様から始めて、ソフトウェアのメカニズムではなく、ソフトウェアの意図を説明します。

これは、アプリケーションの事前定義された仕様に対してシステムを検証することを目的としているため、ビヘイビア駆動開発でよく利用されます。

一般的に知られている仕様テストフレームワークには、 Spock Cucumber Jasmine 、およびRSpecが含まれます。

2.1. Spekとは何ですか?

Spekは、JVM用のKotlinベースの仕様テストフレームワークです。 JUnit5テストエンジンとして機能するように設計されています。 これは、すでにJUnit 5を使用しているプロジェクトに簡単にプラグインして、他のテストと一緒に実行できることを意味します。

必要に応じてJUnitPlatformRunnerの依存関係を使用して、古いJUnit4フレームワークを使用してテストを実行することもできます。

2.2. Mavenの依存関係

Spekを使用するには、必要な依存関係をMavenビルドに追加する必要があります。

<dependency>
    <groupId>org.jetbrains.spek</groupId>
    <artifactId>spek-api</artifactId>
    <version>1.1.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.jetbrains.spek</groupId>
    <artifactId>spek-junit-platform-engine</artifactId>
    <version>1.1.5</version>
    <scope>test</scope>
</dependency>

spek-api 依存関係は、テストフレームワークに使用される実際のAPIです。 これは、テストで使用するすべてのものを定義します。 spek-junit-platform-engine 依存関係は、テストを実行するために必要なJUnit5テストエンジンです。

すべてのSpek依存関係は、互いに同じバージョンである必要があることに注意してください。 最新バージョンはこちらにあります。

2.3. 最初のテスト

Spekを設定すると、テストの作成は、正しいクラスを正しい構造で作成する簡単なケースになります。 これは、読みやすくするために少し珍しいことです。

Spekでは、すべてのテストが適切なスーパークラス(通常は Spek )から継承し、このクラスのコンストラクターにブロックを渡すことでテストを実装する必要があります。

class FirstSpec : Spek({
    // Implement the test here
})

3. テストスタイル

仕様テストでは、可能な限り読みやすい方法でテストを作成することに重点を置いています。 たとえば、Cucumberは、テスト全体を人間が読める言語で記述し、それをステップに結び付けて、コードが分離された状態に保たれるようにします。

Spekは、読み取り可能な文字列として機能する特別なメソッドを使用して動作します。各メソッドには、必要に応じて実行するためのブロックが与えられます。 テストの読み取り方法に応じて、使用する関数にはいくつかのバリエーションがあります。

3.1. 与えられた/オン/それ

テストを作成する1つの方法は、「given / on/it」スタイルです。

これは、 give on 、および it と呼ばれるメソッドを使用して、その構造にネストされ、テストを記述します。

  • give –テストの初期条件を設定します
  • on –テストアクションを実行します
  • it –テストアクションが正しく実行されたことを表明します

各ブロックは必要な数だけ持つことができますが、次の順序でネストする必要があります。

class CalculatorTest : Spek({
    given("A calculator") {
        val calculator = Calculator()
        on("Adding 3 and 5") {
            val result = calculator.add(3, 5)
            it("Produces 8") {
                assertEquals(8, result)
            }
        }
    }
})

このテストは非常に簡単に読み取れます。 テストステップに焦点を当てると、「電卓を与えられた場合、3と5を追加すると8が生成されます」と読むことができます。

3.2. 記述/それ

テストを作成するもう1つの方法は、「describe/it」スタイルです。 代わりに、これはすべてのネストにメソッド describe を使用し、アサーションにitを使用し続けます。

この場合、テストを作成するために必要なだけdescribeメソッドをネストできます。

class CalculatorTest : Spek({
    describe("A calculator") {
        val calculator = Calculator()
        describe("Addition") {
            val result = calculator.add(3, 5)
            it("Produces the correct answer") {
                assertEquals(8, result)
            }
        }
    }
})

このスタイルを使用するテストに適用される構造は少なくなります。つまり、テストの記述方法にはるかに柔軟性があります。

残念ながら、これの欠点は、「given / on/it」を使用した場合ほどテストが自然に読み取られないことです。

3.3. 追加のスタイル

Spekはこれらのスタイルを強制せず、キーワードを必要なだけ交換できるようになります。 唯一の要件は、すべてのアサーションが it 内に存在し、そのレベルで他のブロックが見つからないことです。

使用可能なネストキーワードの完全なリストは次のとおりです。

  • 与えられた
  • の上
  • 説明
  • 環境

これらを使用して、テストをどのように記述したいかについて可能な限り最良の構造をテストに与えることができます。

3.4. データ駆動型テスト

テストの定義に使用されるメカニズムは、すべて単純な関数呼び出しにすぎません。 これは、通常のコードと同様に、それらを使用して他のことを実行できることを意味します。 特に、必要に応じて、データ駆動型の方法でそれらを呼び出すことができます

これを行う最も簡単な方法は、使用するデータをループし、このループ内から適切なブロックを呼び出すことです。

class DataDrivenTest : Spek({
    describe("A data driven test") {
        mapOf(
          "hello" to "HELLO",
          "world" to "WORLD"
        ).forEach { input, expected ->
            describe("Capitalising $input") {
                it("Correctly returns $expected") {
                    assertEquals(expected, input.toUpperCase())
                }
            }
        }
    }
})

必要に応じて、このようなあらゆる種類のことを実行できますが、これがおそらく最も便利です。

4. アサーション

Spekは、アサーションを使用する特定の方法を規定していません。 代わりに、これにより、で最も使いやすいアサーションフレームワークを使用できます。

テストランナーとしてすでにJUnit5フレームワークを使用しているため、明らかな選択はorg.junit.jupiter.api.Assertionsクラスになります。

ただし、テストが改善される場合は、他のアサーションライブラリを使用することもできます。たとえば、 Kluent Expekt HamKrestなどです。

標準のJUnit5 Assertions クラスの代わりにこれらのライブラリを使用する利点は、テストの読みやすさにあります。

たとえば、Kluentを使用して書き直された上記のテストは、次のように読み取られます。

class CalculatorTest : Spek({
    describe("A calculator") {
        val calculator = Calculator()
        describe("Addition") {
            val result = calculator.add(3, 5)
            it("Produces the correct answer") {
                result shouldEqual 8
            }
        }
    }
})

5. ハンドラーの前/後

ほとんどのテストフレームワークと同様に、Spekはテストの前後にロジックを実行することもできます。

これらは、その名前が示すとおり、テスト自体の前または後に実行されるブロックです。

ここでのオプションは次のとおりです。

  • beforeGroup
  • afterGroup
  • beforeEachTest
  • afterEachTest

これらはネストされたキーワードのいずれかに配置でき、そのグループ内のすべてに適用されます。

Spekの動作方法では、ネストされたキーワード内のすべてのコードはテストの開始直後に実行されますが、制御ブロックはitブロックを中心とした特定の順序で実行されます。

Spekは、外側から内側に向かって、同じグループ内にネストされたすべての it ブロックの直前に、各 beforeEachTest ブロックを実行し、すべてのafterEachTestブロックの直後に実行します。 X216X]itブロック。 同様に、Spekは、すべてのグループの直前に各 beforeGroup ブロックを実行し、現在のネスト内のすべてのグループの直後に各afterGroupブロックを実行します。

これは複雑であり、例を使用して最もよく説明されます。

class GroupTest5 : Spek({
    describe("Outer group") {
        beforeEachTest {
            System.out.println("BeforeEachTest 0")
        }
        beforeGroup {
            System.out.println("BeforeGroup 0")
        }
        afterEachTest {
            System.out.println("AfterEachTest 0")
        }
        afterGroup {
            System.out.println("AfterGroup 0")
        }
        
        describe("Inner group 1") {
            beforeEachTest {
                System.out.println("BeforeEachTest 1")
            }
            beforeGroup {
                System.out.println("BeforeGroup 1")
            }
            afterEachTest {
                System.out.println("AfterEachTest 1")
            }
            afterGroup {
                System.out.println("AfterGroup 1")
            }
            it("Test 1") {
                System.out.println("Test 1")
            }
        }
    }
})

上記を実行した結果は次のとおりです。

BeforeGroup 0
BeforeGroup 1
BeforeEachTest 0
BeforeEachTest 1
Test 1
AfterEachTest 1
AfterEachTest 0
AfterGroup 1
AfterGroup 0

すぐに、外側の beforeGroup / afterGroup ブロックがテストのセット全体の周りにあり、内側の beforeGroup /afterGroupブロックが同じコンテキストのテストの周りにあることがわかります。

また、すべての beforeGroup ブロックは、 beforeEachTest ブロックの前に実行され、 afterGroup /afterEachTestの場合は逆に実行されることがわかります。

この大きな例は、複数のグループの複数のテスト間の相互作用を示しており、GitHubでを見ることができます。

6. 被験者

多くの場合、単一のテストサブジェクトに対して単一の仕様を作成します。 Spekは、これを作成するための便利な方法を提供します。これにより、テスト対象のサブジェクトが自動的に管理されます。 このには、Spekクラスの代わりにSubjectSpek基本クラスを使用します。

これを使用する場合、最も外側のレベルでサブジェクトブロックへの呼び出しを宣言する必要があります。 これは、テスト対象を定義します。 次に、これを任意のテストコードからsubject。として参照できます。

これを使用して、以前の計算機テストを次のように書き直すことができます。

class CalculatorTest : SubjectSpek<Calculator>({
    subject { Calculator() }
    describe("A calculator") {
        describe("Addition") {
            val result = subject.add(3, 5)
            it("Produces the correct answer") {
                assertEquals(8, result)
            }
        }
    }
})

それほど多くはないように思われるかもしれませんが、これは、特に考慮すべきテストケースが多数ある場合に、テストをより読みやすくするのに役立ちます。

6.1. Mavenの依存関係

サブジェクト拡張を使用するには、Mavenビルドに依存関係を追加する必要があります。

<dependency>
    <groupId>org.jetbrains.spek</groupId>
    <artifactId>spek-subject-extension</artifactId>
    <version>1.1.5</version>
    <scope>test</scope>
</dependency>

7. 概要

Spekは強力なフレームワークであり、非常に読みやすいテストを可能にします。つまり、組織のすべての部分がテストを読み取ることができます。

これは、すべての同僚がアプリケーション全体のテストに貢献できるようにするために重要です。

最後に、コードスニペットは、いつものように、GitHubにあります。