Spockとは
Spockは、Groovy言語で記述されたBDDスタイルのテストフレームワーク。
JUnit4を基盤としていますが、より高水準のテスト記述が可能で、可読性の高いテストコードの記述が可能。
Spockの特徴
BDDスタイルのテストコードの記述が容易
Spockは、BDDスタイルの記述が容易であり、given-when-then
の記述に基づいて、テストケースを自然な文書化された形式で記述できる。
また、JUnitに比べてテストコードが簡潔で、可読性が高くなるというメリットがある。
データ駆動テストのサポート
Spockは、データ駆動テストをサポートしている。where
ブロックを使用して、入力データを定義することができる。
また、@Unroll
アノテーションを使用することで、入力データに対応するテストケースのラベルを生成することができる。
モックオブジェクトの簡単な作成
Spockは、Groovy言語に組み込まれているメタプログラミングの機能を使用して、モックオブジェクトを簡単に作成することができる。
また、Interaction Based Testing
のような形式で、オブジェクト間の相互作用をテストすることができる。
その他特徴
- 構文がシンプルで読みやすい
- テスト実行速度が速く、JUnitよりも高速
- テストコードを簡潔に書くことができる
- Groovy言語により、Javaよりも簡単に書くことができる
Spockのコーディング例
HTTPリクエストのテスト
class MySpec extends Specification {
def "test GET request to /hello returns 'Hello World!'"() {
given:
def url = "http://localhost:8080/hello"
when:
def response = new URL(url).text
then:
response == "Hello World!"
}
def "test POST request to /hello with 'John' in request body returns 'Hello John!'"() {
given:
def url = "http://localhost:8080/hello"
def requestBody = ["name": "John"]
when:
def response = RestAssured.given()
.contentType(ContentType.JSON)
.body(requestBody)
.post(url)
.andReturn()
.response
.asString()
then:
response == "Hello John!"
}
}
上記の例は、Spockを使用してHTTPリクエストのテスト。
1つ目のテストケースでは、URLにGETリクエストを送信し、レスポンスが"Hello World!"であることをアサートしている。
2つ目のテストケースでは、POSTリクエストを送信して、リクエストボディに"name": "John"を含めている
class SampleSpockSpec extends spock.lang.Specification {
def "1 + 1 は 2 であること"() {
expect:
1 + 1 == 2
}
}
Given-When-Then構文
Spockでは、BDDのGiven-When-Then構文に対応しており、テストコードの可読性が向上する。
以下は、SpockでGiven-When-Then構文を使用したテストコードの例。
class SampleSpockSpec extends spock.lang.Specification {
def "入力された値が2である場合、処理結果は4であること"() {
given:
def inputValue = 2
when:
def result = inputValue * 2
then:
result == 4
}
}
Mockオブジェクトの利用
Spockでは、Mockオブジェクトを利用することができる。
以下は、SpockでMockオブジェクトを利用したテストコードの例。
class SampleSpockSpec extends spock.lang.Specification {
def "SampleServiceの処理結果が正しく返却されること"() {
given:
def sampleService = Mock(SampleService)
sampleService.execute() >> "success"
when:
def result = sampleService.execute()
then:
result == "success"
}
}
例外処理のテスト
以下は、Spockで例外処理をテストするテストコードの例。
class SampleSpockSpec extends spock.lang.Specification {
def "数値が正しくない場合、例外が発生すること"() {
given:
def inputValue = "hoge"
when:
def result = { Integer.parseInt(inputValue) }
then:
def thrownException = thrown(NumberFormatException)
}
}
詳細なコーディング例1: データベース接続のテスト
データベース接続を行うクラスをテストする例です。データベース接続が正常に行われているか、例外が発生するかどうかをテストする。
import spock.lang.*
import groovy.sql.Sql
class DatabaseConnectionSpec extends Specification {
def "正常なデータベース接続が行えること"() {
given:
def sql = Sql.newInstance("jdbc:mysql://localhost/test", "user", "password", "com.mysql.jdbc.Driver")
when:
def result = sql.execute("SELECT COUNT(*) FROM users")
then:
result.rows[0][0] == 3
}
def "異常なデータベース接続時に例外が発生すること"() {
given:
def sql = Sql.newInstance("jdbc:mysql://localhost/unknown", "user", "password", "com.mysql.jdbc.Driver")
when:
def exception = thrown(Exception)
def result = sql.execute("SELECT COUNT(*) FROM users")
then:
exception.message == "Unknown database 'unknown'"
}
}
-
given
ブロックでデータベース接続に必要な情報を準備。 -
when
ブロックでSQLクエリを実行し、結果を取得。 -
then
ブロックで期待する結果をアサート。 - 2つ目のテストケースでは、期待する例外が発生することをアサートするために、
thrown
メソッドを使用。
詳細なコーディング例2: REST APIのテスト
REST APIをテストする例です。HTTPリクエストを送信し、レスポンスをアサートする。
import spock.lang.*
import io.restassured.RestAssured
import io.restassured.http.ContentType
class RestApiSpec extends Specification {
def "GETリクエストが成功すること"() {
when:
def response = RestAssured.get("https://jsonplaceholder.typicode.com/posts/1")
then:
response.statusCode == 200
response.contentType == ContentType.JSON
response.body.title == "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"
}
def "POSTリクエストが成功すること"() {
given:
def body = ["title": "foo", "body": "bar", "userId": 1]
when:
def response = RestAssured.given()
.contentType(ContentType.JSON)
.body(body)
.post("https://jsonplaceholder.typicode.com/posts")
then:
response.statusCode == 201
response.contentType == ContentType.JSON
response.body.title == "foo"
response.body.body == "bar"
}
}
-
when
ブロックでHTTPリクエストを送信し、レスポンスを取得。 -
then
ブロックでレスポンスの結果チェックを行う。
その他コーディング例1: テスト対象クラスのprivateメソッドをテストする
class Foo {
private int add(int a, int b) {
return a + b
}
}
class FooSpec extends spock.lang.Specification {
def "test private method add"() {
given:
def foo = new Foo()
expect:
foo."$spock_private_method"(a, b) == result
where:
a | b | result
1 | 2 | 3
10 | 20 | 30
100 | 200 | 300
}
}
上記の例では、テスト対象の Foo
クラス内に存在する add
メソッドをテストしている。
Spockではprivateメソッドも直接テストすることができます。テストケースのデータパラメータ化には、where
ブロックを使用している。
その他コーディング例2: 引数の異なる複数のテストケースを一括でテストする
class MathUtil {
static int multiply(int a, int b) {
return a * b
}
static int add(int a, int b) {
return a + b
}
}
class MathUtilSpec extends spock.lang.Specification {
def "test multiply"() {
expect:
MathUtil.multiply(a, b) == result
where:
a | b | result
1 | 2 | 2
5 | 5 | 25
}
def "test add"() {
expect:
MathUtil.add(a, b) == result
where:
a | b | result
1 | 2 | 3
5 | 5 | 10
}
}
上記の例では、 MathUtil
クラスの multiply
メソッドと add
メソッドを、複数の異なる引数でテストしています。 where
ブロックを使うことで、異なる入力値と期待値をまとめて定義し、一括でテストすることができる。
参考記述
【Groovy × Spock】Spockでテストを書いてみよう
Spockの基本
JUnit代わりにSpockを使ってみる
Spockを使ってJavaのテストを効率化する