14 Groovy
Groovyは、代替のJVMプログラミング言語です。Javaとの相乗効果が高く、多くの言語機能とライブラリ機能により、魅力的なプログラミング環境となっています。Ratpackは、ratpack-groovy
およびratpack-groovy-test
ライブラリを介して、Groovyとの強力な統合を提供します。GroovyでRatpackアプリケーションを作成すると、一般的に、Javaと比較して、Groovyの簡潔な構文によりコードが少なくなり、より生産的で楽しい開発体験につながります。ただし、明確にするために、RatpackアプリケーションはGroovyで記述する必要はありません。
Groovyは、一般に動的言語として知られています。ただし、Groovy 2.0では、オプションとして完全な静的型付けと静的コンパイルが追加されました。RatpackのGroovyサポートは、完全に「静的Groovy」をサポートするように厳密に設計されており、この目標を達成するためにボイラープレートコードを導入しないように、Groovyの最新機能も活用しています。言い換えれば、RatpackのGroovyサポートは動的言語機能を使用しておらず、強く型付けされたAPIを持っています。
TODO: 静的Groovyを説明する適切なリンクを見つけて、上記で使用する
1.14 前提条件
Groovyを初めて使用する場合は、先に進む前に、次のGroovyの基本トピックを調べてください。
- クロージャ
def
キーワード
TODO: このリストには他に何を含めるべきか?また、リンクが必要
何か他のもの
2.14 Ratpack Groovy API
TODO: Groovy APIがJava APIをラップし、対応するratpack.groovy.*でそれをミラーリングすることを説明する
1.2.14 @DelegatesTo
@DelegatesTo
は、コードを文書化し、IDEおよび静的型チェッカー/コンパイラーにコンパイル時により多くの型情報を提供することを目的としています。アノテーションの適用は、特にDSL作成者にとって興味深いものです。
次のRatpackコードスニペットを検討してみましょう。
ratpack {
handlers {
get {
render "Hello world!"
}
}
}
このコードは、Ratpackの「GroovyChain DSL」で記述されているとしても知られています。本質的に、以下と同じです。
ratpack({
handlers({
get({
render("Hello world!")
})
})
})
メソッドratpack
、handlers
、get
、およびrender
が呼び出されます。実際には、各メソッドを1回だけ呼び出すという制限はなく、たとえば、コードはget
の複数のメソッド呼び出しを持ち、異なるリクエストURIに応答している可能性があります。
ratpack {
handlers {
get {
render "Hello world!"
}
get('foo') {
render "bar"
}
}
}
ratpack
、handlers
、およびget
の呼び出しがどのように階層を構築しているかに注目してください。しかし、実際には、Groovy/Javaオブジェクトへの呼び出しにどのように変換されるのでしょうか?
まさにそこが委譲が活躍する場所です。Groovyでは、Closure
コードブロック内のメソッド呼び出しのターゲットを変更できます。非常に基本的な例を見てみましょう。
class Settings {
String host
Integer port
def host(String h) { this.host = h }
def port(Integer p) { this.port = p }
}
Settings settings(Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
// our DSL starts here and returns a Settings instance
Settings config = settings {
port 1234
host 'localhost'
}
assert config.host == 'localhost'
assert config.port == 1234
settings
DSLブロック内のコードは、現在の字句スコープに存在しないメソッドを呼び出します。ただし、実行時に、delegate
プロパティが設定されると、Groovyは指定されたデリゲートオブジェクト(この場合はSettings
インスタンス)に対してメソッドをさらに解決します。
委譲は、Ratpack DSLの場合と同様に、Groovy DSLで一般的に使用され、DSLコードを基礎となるオブジェクトから分離します。
この手法には、IDEでのコード補完や、Groovy 2で追加された静的型チェッカー/コンパイラーで問題が発生します。groovyConsole
で次のコードを実行します。
class Settings {
String host
Integer port
def host(String h) { this.host = h }
def port(Integer p) { this.port = p }
}
Settings settings(Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
@groovy.transform.TypeChecked
void createConfig() {
Settings config = settings {
port 1234
host 'localhost'
}
assert config.host == 'localhost'
assert config.port == 1234
}
次のようになります。
[Static type checking] - Cannot find matching method ConsoleScript23#port(int). Please check if the declared type is right and if the method exists.
at line: 20, column: 7
[Static type checking] - Cannot find matching method ConsoleScript23#host(java.lang.String). Please check if the declared type is right and if the method exists.
at line: 21, column: 7
型チェッカーは、コンパイル時にデリゲート型Settings
に関する情報を見逃しています。
ここが、@DelegatesTo
が最終的に活躍する場所です。これは、Closure
メソッドパラメーターにこの型情報を指定する必要がある場合に使用されます。
// ...
// let's tell the compiler we're delegating to the Settings class
Settings settings(@DelegatesTo(Settings) Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
// ...
Ratpackは、Closure
メソッドパラメーターが使用される場所であればどこでも@DelegatesTo
を使用します。これは、コード補完や静的型チェッカーを改善するだけでなく、ドキュメントの目的にも役立ちます。
3.14 ratpack.groovy スクリプト
TODO: このファイルで使用されるDSLを紹介し、開発モードでのリロードについて説明する
4.14 handlers {} DSL
TODO:
GroovyChain
DSLと、ハンドラーとしてのクロージャを紹介する
5.14 テスト
Groovyには、テスト作成のための組み込みサポートが付属しています。JUnitの統合サポートに加えて、このプログラミング言語には、テスト駆動開発に非常に価値があることが証明されている機能が付属しています。その1つが、次のセクションで見ていく拡張されたassert
キーワードです。
Ratpack固有のテストサポートドキュメントをお探しの場合は、このマニュアルに専用のRatpackテストガイドセクションがあります。
1.5.14 強力なアサーション
テストを作成するということは、アサーションを使用して前提を策定することを意味します。Javaでは、これはJ2SE 1.4で追加されたassert
キーワードを使用することで実行できます。Javaのアサーションステートメントは、デフォルトで無効になっています。
Groovyには、強力なアサーションステートメントとしても知られる、強力なassert
のバリアントが付属しています。Groovyの強力なassert
は、ブール式がfalse
に検証された場合に与えられる出力で、Javaバージョンと異なります。
def x = 1
assert x == 2
// Output:
//
// Assertion failed:
// assert x == 2
// | |
// 1 false
java.lang.AssertionError
には、元の表明エラーメッセージの拡張バージョンが含まれています。その出力は、外側の式から最も内側の式までのすべての変数と式の値を示しています。
その表現力から、Groovyコミュニティでは、選択したテストライブラリによって提供されるassert*
メソッドの代わりに、assert
ステートメントを使用してテストケースを作成することが一般的な考え方になりました。
assert
ステートメントは、テストを作成するための多くの便利なGroovy機能の1つにすぎません。すべてのGroovy言語テスト機能の包括的なガイドをお探しの場合は、Groovyテストガイドを参照してください。
2.5.14 JUnit 3 サポート
Groovyには、組み込みのJUnit 3サポートと、カスタムTestCase
基本クラスgroovy.util.GroovyTestCase
が付属しています。GroovyTestCase
はTestCase
を拡張し、便利なユーティリティメソッドを追加します。
一例として、shouldFail
メソッドファミリがあります。各shouldFail
メソッドはClosure
を受け取り、実行します。例外がスローされ、コードブロックが失敗した場合、shouldFail
は例外をキャッチし、テストメソッドは失敗しません。
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
}
shouldFail
は、実際にはキャッチされた例外を返すため、例外メッセージまたは型でアサートできます。
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
def msg = shouldFail {
numbers.get(4)
}
assert msg == 'Index: 4, Size: 4'
}
}
さらに、Closure
パラメーターの前にjava.lang.Class
パラメーターが付属するshouldFail
メソッドのバリアントがあります。
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
def msg = shouldFail(IndexOutOfBoundsException) {
numbers.get(4)
}
assert msg == 'Index: 4, Size: 4'
}
}
スローされた例外が別のタイプである場合、shouldFail
は例外を再スローし、テストメソッドを失敗させます。
すべてのGroovyTestCase
メソッドの完全な概要については、JavaDocドキュメントを参照してください。
3.5.14 JUnit 4 サポート
Groovyには、組み込みのJUnit 4サポートが付属しています。Groovy 2.3.0以降、groovy.test.GroovyAssert
クラスは、GroovyのGroovyTestCase
JUnit 3基本クラスを補完するものと見なすことができます。GroovyAssert
は、GroovyでJUnit 4テストを作成するための静的なユーティリティメソッドを追加することにより、org.junit.Assert
を拡張します。
import static groovy.test.GroovyAssert.shouldFail
class ListTest {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
}
すべてのGroovyAssert
メソッドの完全な概要については、JavaDocドキュメントを参照してください。
4.5.14 Spock
Spockは、JavaおよびGroovyアプリケーション用のテストおよび仕様フレームワークです。それを群を抜いて際立たせているのは、その美しく表現力豊かな仕様DSLです。Spock仕様は、Groovyクラスとして記述されます。
Spockは、ユニットテスト、統合テスト、またはBDD(ビヘイビア駆動開発)テストに使用でき、特定のカテゴリのテストフレームワークまたはライブラリに分類されることはありません。
次の段落では、Spock仕様の構造を最初に見ていきます。これは、完全なドキュメントを意味するものではなく、Spockが何を目指しているかについてかなり良い感触を与えることを目的としています。
1.4.5.14 最初のステップ
Spockを使用すると、対象のシステムによって示される機能(プロパティ、側面)を記述する仕様を作成できます。「システム」は、単一のクラスからアプリケーション全体まで、あらゆるものを指す可能性があります。より高度な用語では、仕様対象システムと呼ばれます。機能の説明は、システムの特定の時点のスナップショットとそのコラボレーターから始まります。このスナップショットは、機能のフィクスチャと呼ばれます。
Spock仕様クラスは、spock.lang.Specification
から自動的に派生します。具体的な仕様クラスは、フィールド、フィクスチャメソッド、フィーチャーメソッド、およびヘルパーメソッドで構成される場合があります。
Ratackユニットテスト仕様を見てみましょう。
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import ratpack.test.http.TestHttpClient
import ratpack.test.ServerBackedApplicationUnderTest
class SiteSpec {
ServerBackedApplicationUnderTest aut = new GroovyRatpackMainApplicationUnderTest()
@Delegate TestHttpClient client = TestHttpClient.testHttpClient(aut)
def "Check Site Index"() {
when:
get("index.html")
then:
response.statusCode == 200
response.body.text.contains('<title>Ratpack: A toolkit for JVM web applications</title>')
}
}
Spockフィーチャー仕様は、spock.lang.Specification
クラス内のメソッドとして定義されます。メソッド名の代わりに、文字列リテラルを使用してフィーチャーを記述します。上記の仕様では、"Check Site Index"
を使用して、index.html
へのリクエストの結果をテストします。
フィーチャー仕様では、when
ブロックとthen
ブロックを使用します。when
ブロックは、いわゆる刺激を作成し、刺激への応答を記述するthen
ブロックのコンパニオンです。then
ブロックでassert
ステートメントを省略することもできます。Spockは、そこでブール式を正しく解釈します。setup
ブロックは、フィーチャーメソッド内でのみ表示されるローカル変数を構成するために使用できます。
2.4.5.14 Spock についてさらに詳しく
Spockには、このセクションでは見ていなかったデータテーブルやモックなどの高度な機能が用意されています。詳細については、Spock GitHubページを参照してください。