KotlinとSpekで仕様を書く

1前書き

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

このチュートリアルでは、http://spekframework.org/[Spek framework] - JavaとKotlinの仕様テストフレームワーク - を紹介します。

** 2仕様テストとは

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

その意図は、アプリケーションを事前定義された仕様と照らし合わせてシステムを検証することであるため、これは動作駆動開発でしばしば利用されます。

よく知られている仕様テストフレームワークには、http://spockframework.org/[Spock]、https://cucumber.io/[Cucumber]、https://jasmine.github.io/[Jasmine]、およびhttp://rspecがあります。 .info/[RSpec]

2.1. Spekとは何ですか?

  • Spekは、JVM ** 用のKotlinベースの仕様テストフレームワークです。

リンクとして機能するように設計されています:/junit-5-kotlin[JUnit 5テストエンジン]。

これは、JUnit 5を既に使用している他のテストと同時に実行するプロジェクトに簡単に接続できることを意味します。

必要に応じてJUnitプラットフォームランナーの依存関係を使用して、古いJUnit 4フレームワークを使用してテストを実行することもできます。

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 依存関係は、テストを実行するために必要なJUnit 5テストエンジンです。

すべてのSpek依存関係は互いに同じバージョンである必要があることに注意してください。最新版はhttps://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.jetbrains.spek%22%20AND%20a%3A%22spek-api%22[で見つけることができますここに]。

2.3. 最初のテスト

Spekが設定されたら、テストを書くことは正しいクラスを正しい構造で書く簡単なケースです。読みやすくするために、これは少し変わっています。

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

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

** 3テストスタイル

  • 仕様テストは、できるだけ読みやすいようにテストを書くことを重視しています** 。たとえば、Cucumberはテスト全体を人間が読める言語で書き、それをstepに結び付けてコードを分離します。

  • Spekは、読みやすい文字列** として機能する特別なメソッドを使用して機能します。それぞれのメソッドには、適切に実行するためのブロックが与えられています。テストの読み方によって、使用する関数にはいくつかのバリエーションがあります。

3.1. given / on / it

私たちがテストを書くことができる一つの方法は“ given/on/it”スタイルです。

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

  • given - テストの初期条件を設定します

  • 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. 説明してください

私たちがテストを書くことができるもう一つの方法は“ 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 内に存在し、そのレベルに他のブロックが見つからないことです。

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

  • given

  • on

  • 説明

  • context

これらを使用して、テストの書き方に最適な構造をテストに提供できます。

3.4. データ駆動テスト

テストの定義に使用されるメカニズムは、単純な関数呼び出しに他なりません。これは、通常のコードのように、それらを使って他のことができることを意味します。特に、望むならば、データ駆動型でそれらを呼び出すことができます。

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

…… クラスDataDrivenTest:Spek({     describe( "データ駆動テスト"){         の地図(           「こんにちは」〜「こんにちは」           「世界」から「世界」へ         ).forEach {input、expected - >             describe( "$ inputを大文字にする"){                 それ(「正しく$ expectedを返す」){                     assertEquals(予想される、input.toUpperCase())                 }             }         }     } ) ……

必要ならば、私たちはこのようなあらゆることをすることができますが、これはおそらく最も有用です。

4アサーション

Spekはアサーションを使用する特定の方法を規定していません。その代わりに、私たちが最も満足しているアサーションフレームワークを使用することができます。

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

ただし、テストを改善するために必要な他のアサーションライブラリも使用できます。たとえば、https://github.com/MarkusAmshove/Kluent[Kluent]、https://github.com/winterbe/expekt[Expektなどです。]またはhttps://github.com/npryce/hamkrest[HamKrest]。

標準のJUnit 5 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はテストの前後にロジックを実行することもできます。

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

以下のオプションがあります。

  • 前グループ

  • afterGroup

  • beforeEachTest

  • afterEachTest

これらは、入れ子になっているキーワードのいずれかに配置でき、そのグループ内のすべてに適用されます。

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

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

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

…… クラスGroupTest5:Spek({     describe( "外部グループ"){         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( "内部グループ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")             }             それ( "テスト1"){                 System.out.println( "テスト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 blockの前に実行され、反対の afterGroup/afterEachTest__が実行されることもわかります。

複数のグループにおける複数のテスト間の相互作用を示す、この大きな例はhttps://github.com/eugenp/tutorials/tree/master/core-kotlin[GitHub]で見ることができます。]

6. テスト科目

  • 多くの場合、私たちは一つのテスト科目に対して一つのSpecを書くでしょう 。 Spekはこれを書くための便利な方法を提供しています、それはそれが自動的に私達のためにテスト中の主題を管理するように。 これには Spek クラスの代わりに SubjectSpek 基本クラスを使用します。

  • これを使うとき、一番外側のレベルで subject ブロックへの呼び出しを宣言する必要があります** 。これは被験者を定義します。これで、テストコードからこれを 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の依存関係

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

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

7. 概要

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

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

最後に、いつものように、コードスニペットはhttps://github.com/eugenp/tutorials/tree/master/kotlin-libraries[over GitHub]にあります。