1. 概述
在本篇文章中,我们将深入探讨 Spock 提供的扩展机制。
在编写测试时,我们常常需要对测试生命周期进行定制或增强,比如:
- 条件执行某些测试
- 对不稳定集成测试自动重试
- 控制测试执行顺序
- 设置超时机制
- 标记待实现功能的测试
Spock 提供了丰富的扩展机制,允许我们通过注解或配置文件的方式对测试行为进行灵活控制。
接下来,我们将介绍 Spock 中最常用的扩展用法。
2. Maven 依赖
在开始前,我们需要引入必要的 Maven 依赖项:
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.3-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
<scope>test</scope>
</dependency>
✅ 确保这些依赖已正确添加到你的 pom.xml
文件中。
3. 基于注解的扩展
Spock 的大多数扩展功能都是通过注解实现的。我们可以将这些注解添加到测试类或方法上,以控制测试行为。
3.1. @Ignore
当我们需要临时跳过某些测试时,可以使用 @Ignore
注解。
@Ignore
def "I won't be executed"() {
expect:
true
}
Spock 不会执行该测试方法,大多数 IDE 会将其标记为 skipped。
也可以在类级别使用:
@Ignore
class IgnoreTest extends Specification
还可以添加忽略原因:
@Ignore("probably no longer needed")
3.2. @IgnoreRest
如果你想只执行某个测试方法,其余都跳过,可以使用 @IgnoreRest
:
def "I won't run"() { }
@IgnoreRest
def 'I will run'() { }
def "I won't run too"() { }
⚠️ 注意:这个注解非常有用,尤其是在调试时只运行一个测试方法。
3.3. @IgnoreIf
有时候我们希望根据特定条件跳过测试,比如只在某个操作系统上跳过:
@IgnoreIf({System.getProperty("os.name").contains("windows")})
def "I won't run on windows"() { }
Spock 提供了一些辅助类,简化条件判断:
os
:操作系统信息(spock.util.environment.OperatingSystem
)jvm
:JVM 信息sys
:系统属性env
:环境变量
例如使用 os.isWindows()
:
@IgnoreIf({ os.isWindows() })
def "I'm using Spock helper classes to run only on windows"() {}
3.4. @Requires
有时候我们更倾向于表达“在什么条件下才执行”,而不是“在什么条件下不执行”。这时可以使用 @Requires
:
@Requires({ os.isWindows() })
def "I will run only on Windows"()
与 @IgnoreIf
相比,@Requires
更加语义清晰。
3.5. @PendingFeature
在 TDD 中,我们常常先写测试,再实现功能。此时可以使用 @PendingFeature
标记尚未完成的测试:
@PendingFeature
def 'test for not implemented yet feature. Maybe in the future it will pass'()
区别于 @Ignore
,@PendingFeature
会执行测试,但忽略失败。如果测试通过了,会报错提醒你移除注解。
3.6. @Stepwise
默认情况下,测试方法是无序执行的。如果需要按顺序执行,可以使用 @Stepwise
:
@Stepwise
class StepwiseTest extends Specification {
def 'I will run as first'() { }
def 'I will run as second'() { }
}
⚠️ 警告:测试应尽量独立,避免依赖顺序。但若确实需要,此注解是不错的选择。
3.7. @Timeout
设置测试方法的执行时间限制:
@Timeout(1)
def 'I have one second to finish'() { }
默认单位是秒,也可以指定其他单位:
@Timeout(value = 200, unit = TimeUnit.MILLISECONDS)
def 'I will fail after 200 millis'() { }
类级别使用时,对所有方法生效:
@Timeout(5)
class ExampleTest extends Specification {
@Timeout(1)
def 'I have one second to finish'() {}
def 'I will have 5 seconds timeout'() {}
}
方法级别的注解优先级高于类级别。
3.8. @Retry
对于不稳定的集成测试,可以使用 @Retry
自动重试:
@Retry
def 'I will retry three times'() { }
默认重试 3 次。也可以指定重试条件:
- 重试特定异常:
@Retry(exceptions = [RuntimeException])
def 'I will retry only on RuntimeException'() { }
- 重试特定异常信息:
@Retry(condition = { failure.message.contains('error') })
def 'I will retry with a specific message'() { }
- 带延迟重试:
@Retry(delay = 1000)
def 'I will retry after 1000 millis'() { }
也可以在类级别使用。
3.9. @RestoreSystemProperties
在测试中修改系统属性后,可以使用此注解自动恢复:
@RestoreSystemProperties
def 'all environment variables will be saved before execution and restored after tests'() {
given:
System.setProperty('os.name', 'Mac OS')
}
⚠️ 注意:修改系统属性会影响并发测试,应避免在并发环境下使用。
3.10. 人性化的标题与描述
使用 @Title
添加可读性强的测试类标题:
@Title("This title is easy to read for humans")
class CustomTitleTest extends Specification
使用 @Narrative
添加多行描述:
@Narrative("""
as a user
i want to save favourite items
and then get the list of them
""")
class NarrativeDescriptionTest extends Specification
3.11. @See
为测试方法添加外部参考链接:
@See("https://example.org")
def 'Look at the reference'()
多个链接用列表形式:
@See(["https://example.org/first", "https://example.org/second"])
def 'Look at the references'()
3.12. @Issue
标记测试方法关联的 Issue:
@Issue("https://jira.org/issues/LO-531")
def 'single issue'() {}
@Issue(["https://jira.org/issues/LO-531", "http://jira.org/issues/LO-123"])
def 'multiple issues'()
3.13. @Subject
标记被测试的类:
@Subject
ItemService itemService // 初始化代码...
目前仅用于文档说明。
4. 配置扩展
除了注解,我们还可以通过 SpockConfig.groovy
文件全局配置扩展行为。
通常将其放在 src/test/resources/SpockConfig.groovy
,Spock 会自动读取。
4.1. 控制堆栈信息输出
默认情况下,Spock 会过滤堆栈信息:
runner {
filterStackTrace true
}
关闭后,会显示完整堆栈:
runner {
filterStackTrace false
}
✅ 小贴士:CI 环境建议关闭过滤,本地调试开启更清爽。
4.2. 条件配置
我们可以根据环境变量动态配置:
if (System.getenv("FILTER_STACKTRACE") == null) {
filterStackTrace false
}
4.3. @Issue
的 URL 前缀
统一配置 @Issue
的 URL 前缀:
report {
issueNamePrefix 'Bug '
issueUrlPrefix 'https://jira.org/issues/'
}
这样 @Issue("LO-531")
会被解析为 https://jira.org/issues/LO-531
。
4.4. 优化测试执行顺序
启用测试执行顺序优化,让失败的测试优先执行:
runner {
optimizeRunOrder true
}
该功能默认关闭。
4.5. 排除或包含测试类/方法
通过配置文件排除特定测试类或带有特定注解的方法:
import extensions.TimeoutTest
import spock.lang.Issue
runner {
exclude {
baseClass TimeoutTest
annotation Issue
}
}
也可以使用 include
包含特定测试。
4.6. 生成测试报告
启用测试报告生成:
report {
enabled true
logFileDir '.'
logFileName 'report.json'
logFileSuffix new Date().format('yyyy-MM-dd')
}
报告包含 @Title
, @See
, @Issue
, @Narrative
等注解信息。
也可以通过系统属性控制:
-Dspock.report.enabled=true
-Dspock.logFileDir=.
-Dspock.logFileName=report.json
5. 总结
Spock 的扩展机制非常强大且灵活,涵盖了测试生命周期的各个方面:
- 通过注解控制测试行为
- 通过配置文件统一管理扩展行为
- 支持测试报告生成、执行顺序优化、条件执行、自动重试等高级功能
熟练掌握这些扩展,能极大提升测试效率和可维护性。
完整示例代码可在 GitHub 项目 中找到。