RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【Groovy × Spock】Spockでテストを書いてみよう

はじめに

本記事では、Spockの基礎について解説します。
「そもそもSpockって何?」「コードをテストするってどうやるの?」という方がSpockでテストを書く助けになれば幸いです。

目次

Spockとは

Spockとは、Javaコードのテストができるフレームワークで、Groovyという言語で記述します。
Groovyを知らないという方でも、書き方がJavaに近いのでほとんど同じ感覚で書くことができます。

同じくJavaのコードをテストするフレームワークJUnitがあり、よく比較されます。
どちらを使うかは好みですが、個人的にはSpockの方が直感的にわかりやすいと思っています。
JUnitとSpockを比較した記事があるのでJUnitについても知りたいという方はこちらの記事もおすすめです。

tech-blog.rakus.co.jp

テストコードを書いてみよう

expectブロック

では、さっそくSpockを用いてテストコードを書いていきましょう。
テストするコードはこちらです。
説明するまでもないですが、数字の足し算結果を返すだけのメソッドです。

テストするコード

public static int add(int x, int y) {
  return x + y;
}

テスト

class AddSpec extends Specification {
  def "add関数のテスト"() {
    expect:
    add(2, 3) == 5
  }
}

テストするコードがシンプルなので、テストもシンプルですね。

では、上から順にみていきましょう。

Spockを使うには、Specificationを継承します。
私も中身は知らないのでおまじないだと思ってとりあえず書いておいてください。また、慣例でテストクラス名はメソッド名+Specにすることが多いです。

次に、メソッドの宣言です。Javaとは少し違い、def 名前() {}のように書きます。
名前の部分は、例えばdef addTest() {}のように書くことも、例のように""で囲んで日本語を使うこともできます。

では、メソッドの中を見てみましょう。
expectはGroovyではなく、Spock特有の構文です。
expectの他にgiven(setup)、whenthenwhereclearnupブロックがあり、それぞれ役割があります。(後述)

expectはテストを書くブロックで、add(2, 3)の結果が5だと予測(expect)することを表します。

whereブロック

次に、whereブロックを使ってみましょう。
whereは、同じメソッドを色々なパターンで試すときに使用するブロックです。1つのメソッドを1パターンだけテストすることはほぼないので頻繫に使用することになると思います。

では、例を見てみましょう。テストするのは先程と同じ、足し算するメソッドです。

テスト

class AddSpec extends Specification {
  def "add関数のテスト"() {
    expect:
    add(x, y) == result

    where:
    x                 | y  || result
    2                 | 3  || 5
    Integer.MAX_VALUE | 1  || 2147483648
    Integer.MIN_VALUE | -1 || -2147483649
  }
}

境界値のテストを追加してみました。
このように、1行目に変数、2行目以降にパターンのように書きます。
変数(x, y, result)はメソッド内で使うことができ、例ではaddメソッドの引数と結果を変数にしています。
|は見た通り変数の区切りです。|の数は1個でも2個でも同じ意味ですが、入力と出力などで区切ると見やすさがアップします。

when, thenブロック

実行結果1

add(x, y) == result
|   |  |  |  |
|   |  1  |  2147483648
|   |     false
|   2147483647
-2147483648

実行結果2

add(x, y) == result
|   |  |  |  |
|   |  -1 |  -2147483649
|   |     false
|   -2147483648
2147483647

テストを実行したら失敗しました。
実行結果を見る限りオーバーフローが起きてしまったみたいです。
オーバーフローが起きたときはちゃんと例外を投げるように修正し、例外になるかどうかのテストも追加しましょう。

テストするコード

public static int add(int x, int y) {
  return Math.addExact(x, y);
}

テスト

class AddSpec extends Specification {
  def "add関数のテスト"() {
    expect:
    add(2, 3) == 5
  }

  def "add関数のテスト_例外"() {
    when:
    add(Integer.MAX_VALUE, 1)

    then:
    thrown(ArithmeticException)
  }
}

例外になるかをテストするためにwhenthenブロックを使います。
whenthenは必ずセットで使うブロックで、whenに実行する内容、thenに結果を書きます。例はadd(Integer.MAX_VALUE, 1)を実行したときにArithmeticExceptionが投げられるという意味です。

given(setup)ブロック、cleanupブロック

残りのブロックについても簡単に説明しておきます。
given(setup)はテストをするための準備、cleanupはテストの後処理をするブロックです。

テスト

class AddSpec extends Specification {
  def "readFileのテスト"() {
    given:
    File file = new File("./.txt")
    BufferedReader br = new BufferedReader(new FileReader(file))

    expect:
    def result = readFile(br)

    cleanup:
    br.close()
  }
}

ファイルを読むメソッドのテストを書いてみました。
givenで引数に必要なBufferedReaderを準備してcleanupで解放しています。このように、複雑なクラスを引数に取るときやメモリ解放が必要な時にこれらのブロックを使います。

ちなみに、他のブロックとは違いgiven(setup)ブロックは省略することができます。
逆にそれ以外のブロックは付け忘れるとビルドエラーになるので注意しましょう。

予約メソッド(Fixture methods)

Spockでは、4つの特殊なメソッドが用意されており、特定のタイミングで勝手に実行されます。

  • def setupSpec() {} ・・・テストクラスの最初に実行する
  • def setup() {} ・・・各テストメソッドの最初に実行する
  • def cleanup() {} ・・・各テストメソッドの最後に実行する
  • def cleanupSpec() {} ・・・テストクラスの最後に実行する

以上をまとめると、下図のような実行順になります。
予約メソッドは使わなくても書けますが、使いこなせればより簡潔にテストメソッドを作成することができます。

Mockでテストを分割しよう

ここまでは、1つだけで完結するメソッドのテストをしてきました。ですが、実際はメソッドの中でメソッドを呼ぶことが多く、一つのメソッドで階層深くまでテストするのは現実的ではありません。
そこで呼び出したメソッドだけテストするために、メソッド内のメソッドを「モック化」します。
モック(モックアップ)は見た目だけのハリボテのことで、主にシステムを作る前にお客様に「こんな感じのを作りますよ~」という見本で使われます。

Spockにおいてモックは、呼ばれたら実際には実行せず指定された結果を返すことを意味します。
実際にコードを見たほうがわかると思うので、さっそく例に行きましょう。

テストするコード

private Calculator calc;

public static List<Integer> FourArithmetic(int x, int y) {
  List<Integer> results = new ArrayList<>();

  results.add(calc.add(x, y));
  results.add(calc.sub(x, y));
  results.add(calc.mul(x, y));
  results.add(calc.div(x, y));

  return results;
}

テスト

def "FourArithmeticのテスト"() {
  def calcMock = Mock(Calculator)
  calcMock.add(2, 3) >> 5
  calcMock.sub(2, 3) >> -1
  calcMock.mul(2, 3) >> 6
  calcMock.div(2, 3) >> 0

  when:
  def results = FourArithmetic(2, 3)
  then:
  results == List.of(5, -1, 6, 0)
}

例は、FourArithmeticメソッドのテストです。メソッド内でCalculatorクラスのaddsubmuldivを呼び出していますね。

次にテストを見ていきます。
まずMock(Calculator)Calculatorクラスをモック化します。
次に、モック化すると何を実行してもnullになってしまうので、下の行でメソッドを書き換えます。
例では4つのメソッドを、引数が(2, 3)で呼ばれた時だけ右側の値を返すように書き換えました。((_, _)のようにすることで、どんな引数でも指定の戻り値を戻すよう書き換えることもできます)
テストが成功すれば、Calculatorの中身を無視して、FourArithmeticのリスト操作や引数指定だけがテストできたことになります。

また、今回は紹介しませんがSpyStubも、Mockと同じようにメソッドをモック化することができます。

前述したMockSpyStubなどはSpockの機能ですが、Mockitoという全く別の、モックのためのフレームワークがあります。
Spockでのモック化と同じことができるだけでなく、staticメソッドをモック化したり(Mockito.mockStatic)モック化したメソッドの引数をチェックしたり(Captor)できる便利な機能があるので、興味のある方はそちらも調べてみてください。

おわりに

本記事では、Spockの使い方について解説しました。
本記事は公式ドキュメントを参考に作成しています。より正確な内容を学習したい方は公式ドキュメントを読んでみてください。
では、最後までお読みいただきありがとうございました。

参考資料

tech-blog.rakus.co.jp

qiita.com

spockframework.org


エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
20210916153018
https://career-recruit.rakus.co.jp/career_engineer/

カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com

ラクスDevelopers登録フォーム
20220701175429
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/

イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!

◆TECH PLAY
techplay.jp

◆connpass
rakus.connpass.com

Copyright © RAKUS Co., Ltd. All rights reserved.