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:
GroovyChainDSLと、ハンドラーとしてのクロージャを紹介する
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ページを参照してください。