1. 序章

この記事では、GroovyテストフレームワークであるSpockを見ていきます。 主に、Spockは、Groovy機能を活用することにより、従来のJUnitスタックのより強力な代替手段となることを目指しています。

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

Spockは、Groovyを利用することで、通常のJavaコードでは不可能な、Javaアプリケーションをテストするための新しく表現力豊かな方法を導入しています。 この記事では、いくつかの実用的なステップバイステップの例を使用して、Spockの高レベルの概念のいくつかを探ります。

2. Mavenの依存関係

始める前に、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>

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

3. スポックテストの構造

3.1. 仕様と機能

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

class FirstSpecification extends Specification {

}

Specificationインターフェースを拡張していることに注意してください。 フレームワークを使用できるようにするには、各Spockクラスでこれを拡張する必要があります。 そうすることで、最初の機能を実装できるようになります:

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

コードを説明する前に、Spockで機能と呼ばれるものは、JUnitでテストと呼ばれるものと同義であることに注意してください。 したがって、機能を参照するときは常に、実際にはテストを参照しています。

それでは、機能を分析してみましょう。 そうすることで、Javaとの違いをすぐに確認できるはずです。

最初の違いは、機能メソッド名が通常の文字列として記述されていることです。 JUnitでは、キャメルケースまたはアンダースコアを使用して単語を区切るメソッド名がありましたが、表現力や人間が読める形式ではありませんでした。

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

最後に、アサーションがないことを認識します。 これは、アサーションが暗黙的であり、ステートメントが true に等しい場合に渡され、falseに等しい場合に失敗するためです。 繰り返しになりますが、アサーションについては後ほど詳しく説明します。

3.2. ブロック

JUnitのテストを作成するときに、それをパーツに分割する表現力のある方法がないことに気付く場合があります。 たとえば、ビヘイビア駆動開発をフォローしている場合、コメントを使用してパーツが与えられたときにを示すことになります。

@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. セットアップ(Givenによってエイリアス)–ここでは、テストを実行する前に必要なセットアップを実行します。 これは暗黙のブロックであり、どのブロックにも含まれていないコードがその一部になります
  2. When –これは、テスト対象に刺激を提供する場所です。 つまり、テスト対象のメソッドを呼び出す場所
  3. Then –これはアサーションが属する場所です。 Spockでは、これらは単純なブールアサーションとして評価されますが、これについては後で説明します。
  4. Expect –これは、同じブロック内で刺激アサーションを実行する方法です。 より表現力豊かなものに応じて、このブロックを使用するかどうかを選択できます
  5. クリーンアップ–ここでは、他の方法では取り残されるテスト依存関係リソースを破棄します。 たとえば、ファイルシステムからファイルを削除したり、データベースに書き込まれたテストデータを削除したりすることができます。

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

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機能と組み合わせると、アサーションライブラリの必要性をなくすことができます。 これを実証するために、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のように参照比較を行うのではなく、内部的に equals()メソッドが呼び出されて2つのリストを比較することを意味します。

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

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. データドリブンテストとは何ですか?

基本的に、データ駆動型テストは、異なるパラメーターとアサーションを使用して同じ動作を複数回テストする場合です。 この典型的な例は、数値の2乗などの数学演算をテストすることです。 オペランドのさまざまな順列に応じて、結果は異なります。 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でのDatatablesの使用

JUnitと比較した場合のSpockの簡単な利点の1つは、パラメーター化されたテストをクリーンに実装する方法です。 繰り返しますが、スポックでは、これは次のように知られていますデータ駆動型テスト。 それでは、同じテストをもう一度実装しましょう。今回は、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
  }

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

また、それはテストと並んで、それが行うべき場所に属し、定型文はありません。 このテストは、人間が読める形式の名前で表現力豊かであり、純粋なを期待し、は論理セクションを分割するためにブロックします。

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

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

Condition not satisfied:

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

Expected :1

Actual   :4.0

繰り返しになりますが、Spockは非常に有益なエラーメッセージを表示します。 Datatableのどの行が障害を引き起こしたかとその理由を正確に確認できます。

5. 嘲笑

5.1. モッキングとは何ですか?

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

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

5.2. スポックを使用したモック

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

PaymentGateway paymentGateway = Mock()

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

def paymentGateway = Mock(PaymentGateway)

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

when:
    def result = paymentGateway.makePayment(12.99)

then:
    result == false

これの用語は寛大なモッキングです。 これは、定義されていないモックメソッドが、例外をスローするのではなく、適切なデフォルトを返すことを意味します。 これはSpockの設計によるもので、モックを作成してテストの脆弱性を軽減するためのものです。

5.3. モックでのスタブメソッド呼び出し

さまざまな引数に特定の方法で応答するように、モックで呼び出されるメソッドを構成することもできます。 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. 検証

モックでやりたいもう1つのことは、期待されるパラメーターを使用してさまざまなメソッドがモックで呼び出されたことを表明することです。 言い換えれば、モックとの相互作用を検証する必要があります。

検証の一般的なユースケースは、モックのメソッドにvoidreturnタイプがある場合です。 この場合、操作する結果がないため、テスト対象のメソッドを介してテストするための推定動作はありません。 一般に、何かが返された場合、テスト対象のメソッドはそれを操作でき、その操作の結果が私たちが主張するものになります。

voidreturn型のメソッドが呼び出されることを確認してみましょう。

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

    when:
        notifier.notify('foo')

    then:
        1 * notifier.notify('foo')
}

Spockは、Groovyオペレーターのオーバーロードを再び活用しています。 モックメソッドの呼び出しに1を掛けることで、呼び出されたと予想される回数を示しています。

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

2 * notifier.notify('foo')

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

Too few invocations for:

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

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

2 * notifier.notify(_)

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

2 * notifier.notify(!'foo')

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

6. 結論

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

Groovyを活用することで、テストを通常のJUnitスタックよりも表現力豊かにする方法を示しました。 仕様機能の構造について説明しました。

また、データ駆動型テストの実行がいかに簡単であるか、また、ネイティブのSpock機能を介してモックとアサーションがいかに簡単であるかを示しました。

これらの例の実装は、GitHubにあります。 これはMavenベースのプロジェクトであるため、そのまま実行するのは簡単です。