はじめに
本記事では、Spockの基礎について解説します。
「そもそもSpockって何?」「コードをテストするってどうやるの?」という方がSpockでテストを書く助けになれば幸いです。
目次
Spockとは
Spockとは、Javaコードのテストができるフレームワークで、Groovyという言語で記述します。
Groovyを知らないという方でも、書き方がJavaに近いのでほとんど同じ感覚で書くことができます。
同じくJavaのコードをテストするフレームワークにJUnitがあり、よく比較されます。
どちらを使うかは好みですが、個人的にはSpockの方が直感的にわかりやすいと思っています。
JUnitとSpockを比較した記事があるのでJUnitについても知りたいという方はこちらの記事もおすすめです。
テストコードを書いてみよう
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
)、when
、then
、where
、clearnup
ブロックがあり、それぞれ役割があります。(後述)
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) } }
例外になるかをテストするためにwhen
、then
ブロックを使います。
when
とthen
は必ずセットで使うブロックで、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
クラスのadd
、sub
、mul
、div
を呼び出していますね。
次にテストを見ていきます。
まずMock(Calculator)
でCalculator
クラスをモック化します。
次に、モック化すると何を実行してもnull
になってしまうので、下の行でメソッドを書き換えます。
例では4つのメソッドを、引数が(2, 3)
で呼ばれた時だけ右側の値を返すように書き換えました。((_, _)
のようにすることで、どんな引数でも指定の戻り値を戻すよう書き換えることもできます)
テストが成功すれば、Calculator
の中身を無視して、FourArithmetic
のリスト操作や引数指定だけがテストできたことになります。
また、今回は紹介しませんがSpy
やStub
も、Mock
と同じようにメソッドをモック化することができます。
前述したMock
、Spy
、Stub
などはSpockの機能ですが、Mockitoという全く別の、モックのためのフレームワークがあります。
Spockでのモック化と同じことができるだけでなく、staticメソッドをモック化したり(Mockito.mockStatic)モック化したメソッドの引数をチェックしたり(Captor)できる便利な機能があるので、興味のある方はそちらも調べてみてください。
おわりに
本記事では、Spockの使い方について解説しました。
本記事は公式ドキュメントを参考に作成しています。より正確な内容を学習したい方は公式ドキュメントを読んでみてください。
では、最後までお読みいただきありがとうございました。
参考資料
エンジニア中途採用サイト
ラクスでは、エンジニア・デザイナーの中途採用を積極的に行っております!
ご興味ありましたら是非ご確認をお願いします。
https://career-recruit.rakus.co.jp/career_engineer/
カジュアル面談お申込みフォーム
どの職種に応募すれば良いかわからないという方は、カジュアル面談も随時行っております。
以下フォームよりお申込みください。
rakus.hubspotpagebuilder.com
ラクスDevelopers登録フォーム
https://career-recruit.rakus.co.jp/career_engineer/form_rakusdev/
イベント情報
会社の雰囲気を知りたい方は、毎週開催しているイベントにご参加ください!
◆TECH PLAY
techplay.jp
◆connpass
rakus.connpass.com