14 Groovy
Groovy 是一种替代的 JVM 编程语言。它与 Java 具有很强的协同作用,并且具有许多语言和库功能,使其成为引人注目的编程环境。Ratpack 通过 ratpack-groovy
和 ratpack-groovy-test
库提供了与 Groovy 的强大集成。与 Java 相比,使用 Groovy 编写 Ratpack 应用程序通常会通过 Groovy 简洁的语法而减少代码量,并提供更有效率、更愉快的开发体验。不过要明确的是,Ratpack 应用程序并不 *需要* 用 Groovy 编写。
Groovy 通常被称为动态语言。但是,Groovy 2.0 添加了完整的静态类型和静态编译作为一种选择。Ratpack 的 Groovy 支持严格设计为完全支持“静态 Groovy”,并且还利用 Groovy 的最新功能来避免引入样板代码以实现此目标。换句话说,Ratpack 的 Groovy 支持不使用任何动态语言功能,并且具有强类型 API。
待办事项:找到描述静态 Groovy 的适当链接,并在上面使用。
1.14 先决条件
如果您不熟悉 Groovy,您可能需要在继续之前研究以下 Groovy 基础知识
- 闭包
def
关键字
待办事项:该列表中还应该包含什么?另外,需要链接。
其他内容
2.14 Ratpack Groovy API
待办事项:解释 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
方法。事实上,对每个方法的调用次数没有限制,例如,代码可能对不同的请求 URI 进行了多次 get
方法调用。
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
实例。
委托通常用在 Groovy DSL 中,就像 Ratpack 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 脚本
待办事项:介绍此文件中使用的 DSL,讨论在开发模式下的重新加载。
4.14 handlers {} DSL
待办事项:介绍
GroovyChain
DSL,以及闭包作为处理程序。
5.14 测试
Groovy 自带了编写测试的内置支持。除了对 JUnit 的集成支持外,这种编程语言还具有被证明对测试驱动开发非常有价值的功能。其中之一是扩展的 assert
关键字,我们将在下一节中介绍它。
如果您正在寻找 Ratpack 特定的测试支持文档,本手册中有一个专门的 Ratpack 测试指南 部分。
1.5.14 强断言
编写测试意味着使用断言来制定假设。在 Java 中,这可以通过使用在 J2SE 1.4 中添加的 assert
关键字来实现。Java 中的断言语句默认情况下是禁用的。
Groovy 带有一个强大的 assert
变体,也称为 *强断言语句*。Groovy 的强 assert
与 Java 版本的不同之处在于,当布尔表达式验证为 false
时,它的输出有所不同。
def x = 1
assert x == 2
// Output:
//
// Assertion failed:
// assert x == 2
// | |
// 1 false
java.lang.AssertionError
包含原始断言错误消息的扩展版本。它的输出显示了从最外层表达式到最内层表达式的所有变量和表达式值。
由于其表达能力,在 Groovy 社区中,使用 assert
语句而不是任何由所选测试库提供的 assert*
方法来编写测试用例已成为常识。
assert
语句只是 Groovy 编写测试的众多有用功能之一。如果您正在寻找所有 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'
}
}
此外,还有一个 shouldFail
方法的变体,它在 Closure
参数之前带有一个 java.lang.Class
参数。
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
扩展了 org.junit.Assert
,通过添加用于在 Groovy 中编写 JUnit 4 测试的静态实用程序方法。
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 页面 以获取更多文档。