1. 简介
Groovy 编程语言提供了强大的测试编写支持。除了语言特性和与最先进的测试库和框架的测试集成之外,Groovy 生态系统还诞生了一系列丰富的测试库和框架。
本章将从特定于语言的测试特性开始,然后深入探讨 JUnit 集成、用于规范的 Spock 以及用于功能测试的 Geb。最后,我们将概述其他已知与 Groovy 协同工作的测试库。
2. 语言特性
除了对 JUnit 的集成支持外,Groovy 编程语言还提供了一些特性,这些特性已被证明对测试驱动开发非常有价值。本节将深入探讨这些特性。
2.1. 强大断言
编写测试意味着使用断言来制定假设。在 Java 中,可以使用在 J2SE 1.4 中添加的 assert
关键字来实现。在 Java 中,可以通过 JVM 参数 -ea
(或 -enableassertions
)和 -da
(或 -disableassertions
)来启用 assert
语句。Java 中的断言语句默认情况下是被禁用的。
Groovy 提供了一个相当 强大的 assert
变体,也称为 强大断言语句。Groovy 的强大 assert
与 Java 版本的不同之处在于,当布尔表达式验证为 false
时,它的输出会有所不同。
def x = 1
assert x == 2
// Output: (1)
//
// Assertion failed:
// assert x == 2
// | |
// 1 false
1 | 本节显示了 std-err 输出 |
只要断言无法成功验证,就会抛出的 java.lang.AssertionError
包含原始异常消息的扩展版本。强大断言输出显示了从外到内表达式的评估结果。
强大断言语句的真正力量在复杂的布尔语句或包含集合或其他支持 toString
的类的语句中得以体现。
def x = [1,2,3,4,5]
assert (x << 6) == [6,7,8,9,10]
// Output:
//
// Assertion failed:
// assert (x << 6) == [6,7,8,9,10]
// | | |
// | | false
// | [1, 2, 3, 4, 5, 6]
// [1, 2, 3, 4, 5, 6]
与 Java 的另一个重要区别是,在 Groovy 中,断言是 默认启用的。这是语言设计上的决定,目的是消除禁用断言的可能性。或者,正如 Bertrand Meyer 所说,如果你把脚放在真水中,就没有任何意义把救生圈摘掉
。
需要注意的一点是,强大断言语句中布尔表达式内的具有副作用的方法。由于内部错误消息构建机制只存储对目标实例的引用,因此如果涉及到副作用方法,错误消息文本在渲染时可能会失效。
assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]
// Output:
//
// Assertion failed:
// assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]
// | | |
// | | false
// | [1, 2, 3, 4]
// [1, 2, 3, 4] (1)
1 | 错误消息显示的是集合的实际状态,而不是应用 unique 方法之前的状态。 |
如果你选择提供自定义断言错误消息,可以使用 Java 语法 assert expression1 : expression2 来实现,其中 expression1 是布尔表达式,expression2 是自定义错误消息。但需要注意的是,这将禁用强大断言,并将完全回退到断言错误时的自定义错误消息。 |
2.2. 模拟和存根
Groovy 对多种模拟和存根替代方案提供了极佳的内置支持。使用 Java 时,动态模拟框架非常流行。造成这种情况的一个关键原因是,使用 Java 创建自定义手工模拟非常困难。如果选择,这些框架可以轻松地与 Groovy 一起使用,但创建自定义模拟在 Groovy 中要容易得多。在 Groovy 中,你通常可以使用简单的映射或闭包来构建自定义模拟。
以下部分展示了仅使用 Groovy 语言特性来创建模拟和存根的方法。
2.2.1. 映射强制转换
通过使用映射或 expandos,我们可以非常轻松地将协作者的预期行为整合进来,如下所示。
class TranslationService {
String convert(String key) {
return "test"
}
}
def service = [convert: { String key -> 'some text' }] as TranslationService
assert 'some text' == service.convert('key.text')
as
运算符可用于将映射强制转换为特定类。给定的映射键被解释为方法名称,值(为 groovy.lang.Closure
块)被解释为方法代码块。
需要注意的是,如果你将自定义 java.util.Map 后代类与 as 运算符结合使用,映射强制转换可能会造成干扰。映射强制转换机制直接针对某些集合类,它不会考虑自定义类。 |
2.2.2. 闭包强制转换
as
运算符可以与闭包一起以一种巧妙的方式使用,这对简单场景中的开发人员测试非常有用。我们发现这种技术并不强大到足以让我们放弃动态模拟,但在简单情况下,它仍然非常有用。
包含单个方法的类或接口,包括 SAM(单个抽象方法)类,可用于将闭包块强制转换为给定类型的对象。需要注意的是,为此,Groovy 在内部会创建一个继承自给定类的代理对象。因此,该对象将不是给定类的直接实例。如果例如,生成的代理对象的元类后来被更改,这一点很重要。
让我们来看一个将闭包强制转换为特定类型的示例。
def service = { String key -> 'some text' } as TranslationService
assert 'some text' == service.convert('key.text')
Groovy 支持一项称为隐式 SAM 强制转换的功能。这意味着在运行时能够推断目标 SAM 类型的情况下,as
运算符将变得不必要。这种类型的强制转换可能在测试中很有用,可以模拟整个 SAM 类。
abstract class BaseService {
abstract void doSomething()
}
BaseService service = { -> println 'doing something' }
service.doSomething()
2.2.3. MockFor 和 StubFor
Groovy 模拟和存根类可以在 groovy.mock.interceptor
包中找到。
MockFor
类支持通过允许定义协作者行为的 严格排序的 预期来对类进行(通常为单元)测试。典型的测试场景包括一个测试目标类和一个或多个协作者。在这种情况下,通常希望只测试测试目标类的业务逻辑。实现此目标的一种策略是使用简化的模拟对象来替换协作者实例,以帮助隔离测试目标中的逻辑。MockFor
允许使用元编程来创建此类模拟。协作者的预期行为被定义为行为规范。行为将自动执行和检查。
假设我们的目标类如下所示。
class Person {
String first, last
}
class Family {
Person father, mother
def nameOfMother() { "$mother.first $mother.last" }
}
使用 MockFor
,模拟预期始终与顺序相关,并且它的使用会自动以调用 verify
结束。
def mock = new MockFor(Person) (1)
mock.demand.getFirst{ 'dummy' }
mock.demand.getLast{ 'name' }
mock.use { (2)
def mary = new Person(first:'Mary', last:'Smith')
def f = new Family(mother:mary)
assert f.nameOfMother() == 'dummy name'
}
mock.expect.verify() (3)
1 | 一个新的模拟由 MockFor 的新实例创建。 |
2 | 一个 Closure 被传递给 use ,它使模拟功能生效。 |
3 | 对 verify 的调用检查调用顺序和调用次数是否符合预期。 |
StubFor
类支持通过允许定义协作者行为的 松散排序的 预期来对类进行(通常为单元)测试。典型的测试场景包括一个测试目标类和一个或多个协作者。在这种情况下,通常希望只测试测试目标类的业务逻辑。实现此目标的一种策略是使用简化的存根对象来替换协作者实例,以帮助隔离测试目标中的逻辑。StubFor
允许使用元编程来创建此类存根。协作者的预期行为被定义为行为规范。
与 MockFor
形成对比的是,使用 verify
检查的存根预期与顺序无关,并且它的使用是可选的。
def stub = new StubFor(Person) (1)
stub.demand.with { (2)
getLast{ 'name' }
getFirst{ 'dummy' }
}
stub.use { (3)
def john = new Person(first:'John', last:'Smith')
def f = new Family(father:john)
assert f.father.first == 'dummy'
assert f.father.last == 'name'
}
stub.expect.verify() (4)
1 | 一个新的存根由 StubFor 的新实例创建。 |
2 | with 方法用于将闭包中的所有调用委派给 StubFor 实例。 |
3 | 一个 Closure 被传递给 use ,它使存根功能生效。 |
4 | 对 verify 的调用(可选)检查调用次数是否符合预期。 |
MockFor
和 StubFor
不能用于测试静态编译的类,例如 Java 类或使用 @CompileStatic
的 Groovy 类。要存根和/或模拟这些类,可以使用 Spock 或一个 Java 模拟库。
2.2.4. Expando 元类 (EMC)
Groovy 包含一个特殊的 MetaClass
,称为 ExpandoMetaClass
(EMC)。它允许使用简洁的闭包语法动态添加方法、构造函数、属性和静态方法。
每个 java.lang.Class
都提供了一个特殊的 metaClass
属性,该属性将提供对 ExpandoMetaClass
实例的引用。Expando 元类不限于自定义类,它也可以用于 JDK 类,例如 java.lang.String
。
String.metaClass.swapCase = {->
def sb = new StringBuffer()
delegate.each {
sb << (Character.isUpperCase(it as char) ? Character.toLowerCase(it as char) :
Character.toUpperCase(it as char))
}
sb.toString()
}
def s = "heLLo, worLD!"
assert s.swapCase() == 'HEllO, WORld!'
ExpandoMetaClass
是模拟功能的一个相当不错的候选者,因为它允许进行更高级的操作,例如模拟静态方法。
class Book {
String title
}
Book.metaClass.static.create << { String title -> new Book(title:title) }
def b = Book.create("The Stand")
assert b.title == 'The Stand'
甚至可以模拟构造函数。
Book.metaClass.constructor << { String title -> new Book(title:title) }
def b = new Book("The Stand")
assert b.title == 'The Stand'
模拟构造函数可能看起来像是一种不应该被考虑的黑客行为,但即使在这种情况下也可能有有效的用例。一个例子可以在 Grails 中找到,其中域类构造函数是在运行时使用 ExpandoMetaClass 添加的。这允许域对象在 Spring 应用程序上下文中注册自身,并允许注入由依赖注入容器控制的服务或其他 bean。 |
如果你想在每个测试方法级别更改 metaClass
属性,你需要删除对元类的更改,否则这些更改将在跨测试方法调用时保持不变。通过替换 GroovyMetaClassRegistry
中的元类来删除更改。
GroovySystem.metaClassRegistry.removeMetaClass(String)
另一个选择是注册一个 MetaClassRegistryChangeEventListener
,跟踪更改的类并在你的测试运行时环境的清理方法中删除更改。一个很好的例子可以在 Grails web 开发框架中找到。
除了在类级别使用 ExpandoMetaClass
外,还支持在每个对象级别使用元类。
def b = new Book(title: "The Stand")
b.metaClass.getTitle {-> 'My Title' }
assert b.title == 'My Title'
在这种情况下,元类更改仅与实例相关。根据测试场景,这可能比全局元类更改更适合。
2.3. GDK 方法
以下部分简要概述了可以在测试用例场景中利用的 GDK 方法,例如用于测试数据生成。
2.3.1. Iterable#combinations
combinations
方法添加到 java.lang.Iterable
兼容类中,可用于从包含两个或多个子列表的列表中获取组合列表。
void testCombinations() {
def combinations = [[2, 3],[4, 5, 6]].combinations()
assert combinations == [[2, 4], [3, 4], [2, 5], [3, 5], [2, 6], [3, 6]]
}
该方法可以在测试用例场景中用于生成特定方法调用的所有可能的参数组合。
2.3.2. Iterable#eachCombination
添加在 java.lang.Iterable
上的 eachCombination
方法可用于将函数(在本例中为 groovy.lang.Closure
)应用于 combinations
方法构建的每个组合。
eachCombination
是 GDK 方法,它添加到符合 java.lang.Iterable
接口的所有类中。它对输入列表的每个组合应用一个函数。
void testEachCombination() {
[[2, 3],[4, 5, 6]].eachCombination { println it[0] + it[1] }
}
该方法可以在测试上下文中使用,以使用生成的每个组合调用方法。
2.4. 工具支持
2.4.1. 测试代码覆盖率
代码覆盖率是衡量(单元)测试有效性的有用指标。代码覆盖率高的程序比代码覆盖率低或没有代码覆盖率的程序出错的可能性更低。要获得代码覆盖率指标,通常需要在执行测试之前对生成的字节码进行插桩。支持 Groovy 的工具之一是 Cobertura。
以下代码清单展示了如何在 Groovy 项目的 Gradle 构建脚本中启用 Cobertura 测试覆盖率报告的示例。
def pluginVersion = '<plugin version>'
def groovyVersion = '<groovy version>'
def junitVersion = '<junit version>'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.eriwen:gradle-cobertura-plugin:${pluginVersion}'
}
}
apply plugin: 'groovy'
apply plugin: 'cobertura'
repositories {
mavenCentral()
}
dependencies {
compile "org.codehaus.groovy:groovy-all:${groovyVersion}"
testCompile "junit:junit:${junitVersion}"
}
cobertura {
format = 'html'
includes = ['**/*.java', '**/*.groovy']
excludes = ['com/thirdparty/**/*.*']
}
可以为 Cobertura 覆盖率报告选择多种输出格式,并且可以将测试代码覆盖率报告添加到持续集成构建任务中。
3. 使用 JUnit 进行测试
Groovy 通过以下方式简化了 JUnit 测试。
-
您使用与使用 Java 测试时相同的整体做法,但可以在测试中采用 Groovy 的简洁语法,使其简洁。如果您愿意,甚至可以使用编写测试领域特定语言 (DSL) 的功能。
-
有很多辅助类可以简化许多测试活动。具体细节在某些情况下会因您使用的 JUnit 版本而异。我们将在稍后介绍这些细节。
-
Groovy 的 PowerAssert 机制在您的测试中非常有用。
-
Groovy 认为测试非常重要,您应该像脚本或类一样轻松地运行它们。这就是为什么 Groovy 在使用
groovy
命令或 GroovyConsole 时包含自动测试运行器的原因。这为您提供了除运行测试之外的其他一些选项。
在以下部分,我们将仔细研究 JUnit 3、4 和 5 与 Groovy 的集成。
3.1. JUnit 3
也许支持 JUnit 3 测试的最突出的 Groovy 类是 GroovyTestCase
类。由于它派生自 junit.framework.TestCase
,因此它提供了一系列额外的方法,使 Groovy 中的测试变得轻而易举。
虽然 GroovyTestCase 继承自 TestCase ,但这并不意味着您不能在项目中使用 JUnit 4 功能。事实上,最新的 Groovy 版本附带了一个捆绑的 JUnit 4,它带有一个向后兼容的 TestCase 实现。在 Groovy 邮件列表中,有一些关于是否使用 GroovyTestCase 或 JUnit 4 的讨论,结果表明这主要是一个品味问题,但使用 GroovyTestCase ,您可以免费获得一些方法,这些方法使某些类型的测试更容易编写。 |
在本节中,我们将看一下 GroovyTestCase
提供的一些方法。可以在 groovy.test.GroovyTestCase 的 JavaDoc 文档中找到这些方法的完整列表,不要忘记它继承自 junit.framework.TestCase
,它继承了所有 assert*
方法。
3.1.1. 断言方法
GroovyTestCase
继承自 junit.framework.TestCase
,因此它继承了大量的断言方法,这些方法可以在每个测试方法中调用。
class MyTestCase extends GroovyTestCase {
void testAssertions() {
assertTrue(1 == 1)
assertEquals("test", "test")
def x = "42"
assertNotNull "x must not be null", x
assertNull null
assertSame x, x
}
}
如上所示,与 Java 相比,在大多数情况下可以省略括号,这使得 JUnit 断言方法调用表达式的可读性更高。
GroovyTestCase
添加的一个有趣的断言方法是 assertScript
。它确保给定的 Groovy 代码字符串成功执行,没有任何异常。
void testScriptAssertions() {
assertScript '''
def x = 1
def y = 2
assert x + y == 3
'''
}
3.1.2. shouldFail 方法
shouldFail
可用于检查给定的代码块是否失败。如果它失败,断言成立,否则断言失败。
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
上面的示例使用了基本的 shouldFail
方法接口,该接口接受一个 groovy.lang.Closure
作为单个参数。Closure
实例保存了在运行时应该中断的代码。
如果我们想要断言 shouldFail
发生在特定 java.lang.Exception
类型上,我们可以使用接受 Exception
类作为第一个参数和 Closure
作为第二个参数的 shouldFail
实现来做到这一点。
void testInvalidIndexAccess2() {
def numbers = [1,2,3,4]
shouldFail IndexOutOfBoundsException, {
numbers.get(4)
}
}
如果抛出任何不是 IndexOutOfBoundsException
(或它的后代类)的异常,则测试用例将失败。
shouldFail
的一个非常不错的功能到目前为止还没有显现出来:它返回异常消息。如果您想断言异常错误消息,这非常有用。
void testInvalidIndexAccess3() {
def numbers = [1,2,3,4]
def msg = shouldFail IndexOutOfBoundsException, {
numbers.get(4)
}
assert msg.contains('Index: 4, Size: 4') ||
msg.contains('Index 4 out-of-bounds for length 4') ||
msg.contains('Index 4 out of bounds for length 4')
}
3.1.3. notYetImplemented 方法
notYetImplemented
方法深受 HtmlUnit 的影响。它允许编写测试方法,但将其标记为尚未实现。只要测试方法失败并标记为 notYetImplemented
,测试就会变为绿色。
void testNotYetImplemented1() {
if (notYetImplemented()) return (1)
assert 1 == 2 (2)
}
1 | 调用 notYetImplemented 是 GroovyTestCase 获取当前方法堆栈所必需的。 |
2 | 只要测试评估结果为 false ,测试执行就会成功。 |
notYetImplemented
方法的替代方法是 @NotYetImplemented
注释。它允许将方法注释为尚未实现,具有与 GroovyTestCase#notYetImplemented
完全相同的行为,但不需要 notYetImplemented
方法调用。
@NotYetImplemented
void testNotYetImplemented2() {
assert 1 == 2
}
3.2. JUnit 4
Groovy 可用于编写 JUnit 4 测试用例,没有任何限制。groovy.test.GroovyAssert
包含各种静态方法,这些方法可以用作 JUnit 4 测试中 GroovyTestCase
方法的替代方法。
import org.junit.Test
import static groovy.test.GroovyAssert.shouldFail
class JUnit4ExampleTests {
@Test
void indexOutOfBoundsAccess() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
}
如上面的示例所示,GroovyAssert
中的静态方法是在类定义的开头导入的,因此 shouldFail
的使用方法与在 GroovyTestCase
中使用方法相同。
groovy.test.GroovyAssert 派生自 org.junit.Assert ,这意味着它继承了所有 JUnit 断言方法。但是,随着 power assertion 语句的引入,事实证明*最好依靠 assertion 语句*,而不是使用 JUnit 断言方法,其中改进的消息是主要原因。 |
值得一提的是,GroovyAssert.shouldFail
与 GroovyTestCase.shouldFail
并不完全相同。虽然 GroovyTestCase.shouldFail
返回异常消息,但 GroovyAssert.shouldFail
返回异常本身。获取消息需要多按几下键,但作为回报,您可以访问异常的其他属性和方法。
@Test
void shouldFailReturn() {
def e = shouldFail {
throw new RuntimeException('foo',
new RuntimeException('bar'))
}
assert e instanceof RuntimeException
assert e.message == 'foo'
assert e.cause.message == 'bar'
}
3.3. JUnit 5
在使用 JUnit5 时,JUnit4 下描述的大部分方法和辅助类都适用,但 JUnit5 在编写测试时使用了一些略有不同的类注释。有关更多详细信息,请参阅 JUnit5 文档。
根据正常的 JUnit5 指南创建您的测试类,如以下示例所示。
class MyTest {
@Test
void streamSum() {
assertTrue(Stream.of(1, 2, 3)
.mapToInt(i -> i)
.sum() > 5, () -> "Sum should be greater than 5")
}
@RepeatedTest(value=2, name = "{displayName} {currentRepetition}/{totalRepetitions}")
void streamSumRepeated() {
assert Stream.of(1, 2, 3).mapToInt(i -> i).sum() == 6
}
private boolean isPalindrome(s) { s == s.reverse() }
@ParameterizedTest (1)
@ValueSource(strings = [ "racecar", "radar", "able was I ere I saw elba" ])
void palindromes(String candidate) {
assert isPalindrome(candidate)
}
@TestFactory
def dynamicTestCollection() {[
dynamicTest("Add test") { -> assert 1 + 1 == 2 },
dynamicTest("Multiply Test", () -> { assert 2 * 3 == 6 })
]}
}
1 | 此测试需要额外的 org.junit.jupiter:junit-jupiter-params 依赖项,如果您的项目中还没有的话。 |
如果您的 IDE 或构建工具支持并配置为 JUnit5,您可以在其中运行测试。如果您在 GroovyConsole 或通过 groovy
命令运行上述测试,您将看到运行测试结果的简短文本摘要。
JUnit5 launcher: passed=8, failed=0, skipped=0, time=246ms
更详细的信息在 FINE
日志级别可用。您可以配置您的日志以显示此类信息,也可以按以下方式以编程方式执行此操作。
@BeforeAll
static void init() {
def logger = Logger.getLogger(LoggingListener.name)
logger.level = Level.FINE
logger.addHandler(new ConsoleHandler(level: Level.FINE))
}
4. 使用 Spock 进行测试
Spock 是一个用于 Java 和 Groovy 应用程序的测试和规范框架。它与众不同的地方在于它具有美观且表达能力极强的规范 DSL。在实践中,Spock 规范以 Groovy 类编写。虽然是用 Groovy 编写的,但它们可以用来测试 Java 类。Spock 可用于单元测试、集成测试或 BDD(行为驱动开发)测试,它不将自己归类到任何特定的测试框架或库类别。
除了这些出色的功能之外,Spock 还是一个很好的例子,说明了如何在第三方库中利用高级 Groovy 编程语言功能,例如,通过使用 Groovy AST 转换。 |
本节不应作为 Spock 使用指南,它应该让您对 Spock 是什么以及如何利用它进行单元测试、集成测试、功能测试或任何其他类型的测试有一个印象。 |
下一节,我们将初步了解 Spock 规范的结构。它应该让您对 Spock 的工作原理有一个很好的了解。
4.1. 规范
Spock 允许您编写规范,这些规范描述了系统所具有的功能(属性、方面)。“系统”可以是任何东西,从单个类到整个应用程序,它更高级的术语是*被规范的系统*。*功能描述* 从系统的特定快照及其协作者开始,此快照称为*功能的夹具*。
Spock 规范类派生自 spock.lang.Specification
。具体的规范类可能包含字段、夹具方法、功能方法和辅助方法。
让我们看一下一个具有单个功能方法的简单规范,该方法用于一个虚构的 Stack
类。
class StackSpec extends Specification {
def "adding an element leads to size increase"() { (1)
setup: "a new stack instance is created" (2)
def stack = new Stack()
when: (3)
stack.push 42
then: (4)
stack.size() == 1
}
}
1 | 功能方法,按照惯例,以字符串文字命名。 |
2 | 设置块,这是完成此功能的任何设置工作的地方。 |
3 | When 块描述一个刺激,此功能规范的目标下的某个动作。 |
4 | Then 块,任何可以用来验证 when 块触发的代码结果的表达式。 |
Spock 功能规范被定义为 spock.lang.Specification
类中的方法。它们通过使用字符串文字而不是方法名来描述功能。
功能方法包含多个块,在我们的示例中,我们使用了 setup
、when
和 then
。setup
块很特殊,因为它可选,并且允许配置在功能方法内部可见的局部变量。when
块定义了刺激,并且是 then
块的伴侣,then
块描述了对刺激的响应。
请注意,上面 StackSpec
中的 setup
方法还包含一个描述字符串。描述字符串是可选的,可以在块标签(如 setup
、when
、then
)之后添加。
4.2. 更多 Spock
Spock 提供更多功能,例如数据表或高级模拟功能。 请随时咨询 Spock GitHub 页面 以获取更多文档和下载信息。
5. 使用 Geb 进行功能测试
Geb 是一个功能性 Web 测试和抓取库,它与 JUnit 和 Spock 集成。 它基于 Selenium web 驱动程序,并且像 Spock 一样提供 Groovy DSL 来为 web 应用程序编写功能测试。
Geb 具有许多出色功能,使其成为功能测试库的理想选择
-
通过类似 JQuery 的
$
函数访问 DOM -
实现页面模式
-
支持使用模块将某些 web 组件(例如菜单栏等)模块化
-
通过 JS 变量与 JavaScript 集成
本节不应作为 Geb 使用指南,而应提供 Geb 的概览以及如何利用它进行功能测试。 |
下一节将举例说明如何使用 Geb 为具有单个搜索字段的简单网页编写功能测试。
5.1. Geb 脚本
尽管 Geb 可以独立在 Groovy 脚本中使用,但在许多情况下它与其他测试框架结合使用。 Geb 附带了各种基类,可用于 JUnit 3、4、TestNG 或 Spock 测试。 基类是附加 Geb 模块的一部分,需要将其添加为依赖项。
例如,以下 @Grab
依赖项可用于在 JUnit4 测试中使用 Selenium Firefox 驱动程序运行 Geb。 用于 JUnit 3/4 支持的模块是 geb-junit4
@Grab('org.gebish:geb-core:0.9.2')
@Grab('org.gebish:geb-junit4:0.9.2')
@Grab('org.seleniumhq.selenium:selenium-firefox-driver:2.26.0')
@Grab('org.seleniumhq.selenium:selenium-support:2.26.0')
Geb 中的核心类是 geb.Browser
类。顾名思义,它用于浏览页面并访问 DOM 元素
import geb.Browser
import org.openqa.selenium.firefox.FirefoxDriver
def browser = new Browser(driver: new FirefoxDriver(), baseUrl: 'http://myhost:8080/myapp') (1)
browser.drive {
go "/login" (2)
$("#username").text = 'John' (3)
$("#password").text = 'Doe'
$("#loginButton").click()
assert title == "My Application - Dashboard"
}
1 | 创建一个新的 Browser 实例。在这种情况下,它使用 Selenium FirefoxDriver 并设置 baseUrl 。 |
2 | go 用于导航到 URL 或相对 URI |
3 | $ 与 CSS 选择器一起用于访问 username 和 password DOM 字段。 |
Browser
类带有一个 drive
方法,该方法将所有方法/属性调用委托给当前 browser
实例。 Browser
配置不必内联完成,例如,它也可以在 GebConfig.groovy
配置文件中外部化。 在实践中,Browser
类的使用主要被 Geb 测试基类隐藏。 它们将所有缺少的属性和方法调用委托给后台存在的当前 browser
实例
class SearchTests extends geb.junit4.GebTest {
@Test
void executeSeach() {
go 'http://somehost/mayapp/search' (1)
$('#searchField').text = 'John Doe' (2)
$('#searchButton').click() (3)
assert $('.searchResult a').first().text() == 'Mr. John Doe' (4)
}
}
1 | Browser#go 接受一个相对或绝对链接并调用该页面。 |
2 | Browser#$ 用于访问 DOM 内容。 允许使用底层 Selenium 驱动程序支持的任何 CSS 选择器 |
3 | click 用于点击按钮。 |
4 | $ 用于从 searchResult 块中获取第一个链接 |
上面的示例展示了使用 JUnit 4 基类 geb.junit4.GebTest
的简单 Geb web 测试。 请注意,在这种情况下,Browser
配置是外部化的。 GebTest
将 go
和 $
等方法委托给底层的 browser
实例。
5.2. 更多 Geb
在上一节中,我们只是触及了 Geb 可用功能的表面。 关于 Geb 的更多信息可以在 项目主页 上找到。