SpockとGroovyによるテスト入門

1前書き

この記事では、http://spockframework.org/[Spock]、http://groovy-lang.org/[Groovy]のテストフレームワークを見てみましょう。主に、SpockはGroovyの機能を利用することによって、従来のJUnitスタックに代わるより強力な代替手段を目指しています。

Groovyは、Javaとシームレスに統合するJVMベースの言語です。相互運用性に加えて、動的であること、オプションの型を持つこと、メタプログラミングなど、追加の言語概念を提供します。

Groovyを利用することによって、Spockは私たちのJavaアプリケーションをテストするための新しく表現豊かな方法を紹介します。それは普通のJavaコードでは不可能です。この記事では、Spockの高度な概念のいくつかについて、実際的なステップバイステップの例を使って説明します。

2 Mavenの依存関係

始める前に、私たちのhttps://search.maven.org/classic/#search%7Cga%7C1%7C%20(g%3A%22org.spockframework%22%20AND%20a%3A%22spock-coreを追加しましょう。 %22)%20OR%20(g%3A%22org.codehaus.groovy%22%20AND%20a%3A%22groovy-all%22)[Mavenの依存関係]:

<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-core</artifactId>
    <version>1.0-groovy-2.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.codehaus.groovy</groupId>
    <artifactId>groovy-all</artifactId>
    <version>2.4.7</version>
    <scope>test</scope>
</dependency>

標準ライブラリと同様に、SpockとGroovyの両方を追加しました。

しかし、Groovyは新しいJVM言語なので、コンパイルして実行できるようにするには gmavenplus プラグインを含める必要があります。

<plugin>
    <groupId>org.codehaus.gmavenplus</groupId>
    <artifactId>gmavenplus-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>testCompile</goal>
            </goals>
        </execution>
     </executions>
</plugin>

これで最初のSpockテストを書く準備が整いました。これはGroovyコードで書かれます。 GroovyとSpockはテスト目的でのみ使用されているため、これらの依存関係がテストスコープになっているのはこのためです。

3スポックテストの構造

3.1. 仕様と機能

Groovyでテストを書いているので、 src/test/java. の代わりに src/test/groovy ディレクトリに追加する必要があります。このディレクトリに最初のテストを作成し、__Specification.groovyという名前を付けます。

class FirstSpecification extends Specification {

}

Specification インターフェースを拡張していることに注意してください。各Spockクラスは、フレームワークを利用できるようにするためにこれを拡張する必要があります。

これにより、最初の__機能を実装することができます。

def "one plus one should equal two"() {
  expect:
  1 + 1 == 2
}

コードを説明する前に、Spockで feature と呼んでいることがJUnitで test として見ていることと多少同義であることも注目に値します。だから** 私たちが feature を参照するときはいつでも、私たちは実際には__testを参照しています。

それでは、私たちの 特徴 を分析しましょう。そうすることで、私たちはすぐにそれとJavaの間のいくつかの違いを見ることができるはずです。

最初の違いは、フィーチャメソッド名が通常の文字列として書かれていることです。 JUnitでは、キャメルケースやアンダースコアを使って単語を区切るメソッド名があったはずですが、それは表現力も人間も判読可能ではありませんでした。

次のテストコードは expect ブロック内にあります。ブロックについてもう少し詳しく説明しますが、基本的にこれらはテストのさまざまなステップを分割する論理的な方法です。

最後に、我々は主張がないことを理解しています。これは、アサーションが暗黙的で、ステートメントが true に等しいときにパスし、 false に等しいときに失敗するためです。繰り返しになりますが、アサーションについては後でもう少し詳しく説明します。

3.2. ブロック

JUnitをテストとして書くとき、時々、それを部分に分割する表現的な方法がないことに気づくかもしれません。例えば、振る舞い主導の開発に従っていた場合、コメントを使用して__given when部を示すことになるかもしれません。

@Test
public void givenTwoAndTwo__whenAdding__thenResultIsFour() {
  //Given
   int first = 2;
   int second = 4;

  //When
   int result = 2 + 2;

  //Then
   assertTrue(result == 4)
}

Spockはこの問題をブロックで解決しています。 ** ブロックは、ラベルを使用してテストのフェーズを分割するためのSpockのネイティブな方法です。

  1. Setup (Givenによる別名) - ここでは、

テストが実行されます。これは暗黙のブロックで、コードはどのブロックにも含まれていません。 すべてがその一部になっている 。 When - これはテスト中のものに 刺激 を提供する場所です。

言い換えれば、テスト中のメソッドを呼び出す場所です。 。 Then - これはアサーションが属する場所です。 Spockでは、これらは

プレーンなブールアサーションとして評価されます。これについては後で説明します。 。 期待 - これは私たちの 刺激 主張 を実行する方法です

同じブロック内より表現力に富んだものによっては、 またはこのブロックを使用することを選択しないかもしれません 。 Cleanup - ここでは、テスト依存関係のあるリソースをすべて破棄します。

そうでなければ取り残されるでしょう。たとえば、ファイルシステムからファイルを削除したり、データベースに書き込まれたテストデータを削除したりすることができます。

もう一度テストを実装してみましょう。今回はブロックを最大限に活用します。

def "two plus two should equal four"() {
    given:
        int left = 2
        int right = 2

    when:
        int result = left + right

    then:
        result == 4
}

ご覧のとおり、ブロックはテストを読みやすくするのに役立ちます。

3.3. アサーションにGroovyの機能を利用する

  • then および expect ブロック内では、アサーションは暗黙的です** 。

ほとんどの場合、すべてのステートメントが評価され、それが true ではない場合は失敗します。

これをGroovyのさまざまな機能と組み合わせると、アサーションライブラリが不要になります。これを実証するためにhttps://docs.oracle.com/javase/8/docs/api/java/util/List.html[ list ]アサーションを試してみましょう。

def "Should be able to remove from list"() {
    given:
        def list =[1, 2, 3, 4]
    when:
        list.remove(0)

    then:
        list ==[2, 3, 4]}

この記事ではGroovyについて簡単に触れていますが、ここで何が起こっているのかを説明する価値があります。

まず、Groovyはリストを作成するためのより簡単な方法を提供します。要素を角括弧で宣言するだけで、内部的に list がインスタンス化されます。

次に、Groovyは動的なので、 def を使用できます。これは、変数の型を宣言していないことを意味します。

最後に、テストを単純化するという意味で、実証されている最も便利な機能は演算子のオーバーロードです。つまり、内部的には、Javaのように参照を比較するのではなく、2つのリストを比較するために equals() メソッドが呼び出されます。

テストが失敗したときに何が起こるかを説明する価値もあります。それを壊して、コンソールへの出力を見てみましょう。

Condition not satisfied:

list ==[1, 3, 4]|    |
|    false[2, 3, 4] <Click to see difference>

at FirstSpecification.Should be able to remove from list(FirstSpecification.groovy:30)

2つのリストで equals() を呼び出すだけでよいのに対して、Spockは失敗したアサーションの詳細を実行するのに十分なインテリジェントな情報を提供し、デバッグに役立つ情報を提供します。

** 3.4. アサーション例外

**

Spockはまた、例外をチェックするための表現的な方法も提供しています。 JUnitでは、いくつかの選択肢は try-catch ブロックを使用するか、テストの先頭で expected を宣言するか、サードパーティのライブラリを使用することです。 Spockのネイティブアサーションには、箱から出してすぐに例外を処理する方法があります。

def "Should get an index out of bounds when removing a non-existent item"() {
    given:
        def list =[1, 2, 3, 4]
    when:
        list.remove(20)

    then:
        thrown(IndexOutOfBoundsException)
        list.size() == 4
}

ここでは、追加のライブラリを導入する必要はありません。もう1つの利点は、 thrown() メソッドが例外の種類を表明しますが、テストの実行を停止しないことです。

4データ駆動テスト

4.1. データ駆動テストとは何ですか?

基本的に、** データ駆動テストは、異なるパラメータとアサーションを使用して同じ動作を複数回テストする場合です。この典型的な例は、数の二乗などの数学演算のテストです。オペランドのさまざまな組み合わせによって、結果は異なります。 Javaでは、私たちがより馴染みのある用語はパラメータ化されたテストです。

4.2. Java でパラメータ化テストを実装する

状況によっては、JUnitを使用してパラメータ化テストを実装する価値があります。

@RunWith(Parameterized.class)
public class FibonacciTest {
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
          { 1, 1 }, { 2, 4 }, { 3, 9 }
        });
    }

    private int input;

    private int expected;

    public FibonacciTest (int input, int expected) {
        this.input = input;
        this.expected = expected;
    }

    @Test
    public void test() {
        assertEquals(fExpected, Math.pow(3, 2));
    }
}

ご覧のとおり、かなりの冗長性があり、コードはあまり読みやすくありません。テストの外にある2次元のオブジェクト配列と、さまざまなテスト値を注入するためのラッパーオブジェクトを作成する必要がありました。

4.3. Spock でデータテーブルを使う

JUnitと比較してSpockが簡単に勝てることの1つは、パラメータ化されたテストをきれいに実装する方法です。 Spockでは、これは Data Driven Testingとして知られています。 今度は、同じテストをもう一度実装しましょう。今回は、 Data Tables でSpockを使用します。これは、パラメータ化テストを実行するはるかに便利な方法です。 :

def "numbers to the power of two"(int a, int b, int c) {
  expect:
      Math.pow(a, b) == c

  where:
      a | b | c
      1 | 2 | 1
      2 | 2 | 4
      3 | 2 | 9
  }

ご覧のとおり、すべてのパラメータを含む直接的で表現力豊かなデータテーブルがあります。

また、テストと並んで、実行すべき場所に属し、定型句はありません。このテストは、人間が読める名前と、論理セクションを分割するための純粋な expect および where ブロックを使用して表現力豊かです。

4.4. データテーブルが失敗したとき

テストが失敗したときに何が起こるかを確認する価値もあります。

Condition not satisfied:

Math.pow(a, b) == c
     |   |  |  |  |
     4.0 2  2  |  1
               false

Expected :1

Actual   :4.0

繰り返しますが、Spockは非常に有益なエラーメッセージを表示します。データテーブルのどの行が失敗したのか、そしてその理由は正確にわかります。

5モッキング

** 5.1. モッキングとは

モッキングは、テスト対象のサービスが連携するクラスの動作を変更する方法です。依存関係から切り離してビジネスロジックをテストすることができるというのは便利な方法です。

この典型的な例は、ネットワーク呼び出しを行うクラスを単にふりをするものに置き換えることです。より詳細な説明については、リンクを読む価値があります:/mockito-vs-easymock-vs-jmockit[この記事]。

5.2. Spockを使ったモック

Spockは独自のモックフレームワークを持ち、GroovyによってJVMにもたらされた興味深い概念を利用しています。まず、 Mockをインスタンス化しましょう:

PaymentGateway paymentGateway = Mock()

この場合、モックの型は変数の型によって推測されます。 Groovyは動的言語なので、型引数を指定することもできます。これにより、モックを特定の型に割り当てる必要がなくなります。

def paymentGateway = Mock(PaymentGateway)

これで、 PaymentGateway mock __、 __のメソッドを呼び出すたびに、実際のインスタンスが呼び出されることなく、デフォルトの応答が返されます。

when:
    def result = paymentGateway.makePayment(12.99)

then:
    result == false

これに対する用語は lenient mocking です。つまり、例外をスローするのではなく、定義されていないモックメソッドが適切なデフォルト値を返すということです。これはSpockの仕様によるもので、モックを作成してテストを脆弱にするためのものです。

5.3. スタブメソッドが Mocks を呼び出す

また、モックで呼び出されるメソッドを、異なる方法で特定の方法で応答するように設定することもできます。 20の支払いをするときに、 PaymentGateway モックに true を返させるようにしましょう:

given:
    paymentGateway.makePayment(20) >> true

when:
    def result = paymentGateway.makePayment(20)

then:
    result == true

ここで面白いのは、SpockがGroovyの演算子のオーバーロードを利用してメソッド呼び出しをスタブする方法です。 Javaでは、実際のメソッドを呼び出す必要があります。これは、結果として得られるコードがより冗長で表現性が低い可能性があることを意味しています。

それでは、さらにいくつかの種類のスタブを試してみましょう。

メソッドの引数について考えるのをやめて、常に trueを返したい場合は、 を使用することもできます。

paymentGateway.makePayment(__) >> true

異なるレスポンスを交互に使いたい場合は、各要素が順番に返されるリストを提供できます。

paymentGateway.makePayment(__) >>>[true, true, false, true]----

より多くの可能性があります、そしてこれらはモックに関するより高度な将来の記事でカバーされるかもしれません。

====  **  5.4. 検証**

我々がモックでしたいと思うかもしれないもう一つのことは様々なメソッドがそれらに期待されたパラメータで呼ばれたということです。言い換えれば、私たちはモックとの相互作用を検証するべきです。

検証の典型的なユースケースは、私たちのモックのメソッドが__void__戻り型を持っている場合です。この場合、私たちが操作する結果がないということで、私たちがテスト中のメソッドでテストするための推論された行動はありません。一般に、何かが返された場合、テスト中のメソッドがそれを操作することができます。その操作の結果は、私たちが主張するものになります。

void型の戻り値を持つメソッドが呼び出されることを確認してみましょう。

[source,java,gutter:,true]

def "Should verify notify was called"() { given: def notifier = Mock(Notifier)

when:
    notifier.notify('foo')
    then:
        1 **  notifier.notify('foo')
}
Spockは再びGroovyオペレータのオーバーロードを利用しています。私たちのmocksメソッド呼び出しに1を掛けることによって、私たちはそれが呼ばれたと何回期待するかを言っています。

私たちのメソッドがまったく呼び出されていなかったり、指定した回数だけ呼び出されていなかった場合は、私たちのテストでは有益なSpockエラーメッセージを返すことができなかったでしょう。これが2回呼び出されたことを期待して証明しましょう。

[source,java,gutter:,true]

2 ** notifier.notify('foo')

これに続いて、エラーメッセージがどのように見えるかを見てみましょう。いつもどおりです。それは非常に有益です。

[source,java,gutter:,true]

Too few invocations for:

2 ** notifier.notify('foo') (1 invocation)

スタブのように、より緩やかな検証マッチングも実行できます。メソッドパラメータが何であるかを気にしない場合は、アンダースコアを使用できます。

[source,java,gutter:,true]

2 ** notifier.notify(__)

特定の引数で呼び出されなかったことを確認したい場合は、not演算子を使用できます。

[source,java,gutter:,true]

2 ** notifier.notify(!'foo')

繰り返しますが、より多くの可能性があり、それらは将来のより高度な記事でカバーされるかもしれません。

===  **  6. 結論**

この記事では、Spockを使ったテストを簡単に説明しました。

Groovyを利用することで、テストを典型的なJUnitスタックよりも表現力豊かにする方法を実証しました。 __specifications__と__features__の構造について説明しました。

また、データ駆動型テストを実行することがどれほど簡単であるか、そしてネイティブのSpock機能を介してモック化とアサーションがどのように簡単であるかについても説明しました。

これらの例の実装はhttps://github.com/eugenp/tutorials/tree/master/testing-modules/groovy-spock[GitHubに掲載]を参照してください。これはMavenベースのプロジェクトなので、そのまま実行するのは簡単です。