1. Overview
1.概述
In this tutorial, we’ll take a look at Spock extensions.
在本教程中,我们将看一下Spock扩展。
Sometimes, we might need to modify or enhance our spec’s lifecycle. For example, we’d like to add some conditional execution, retry on randomly failing integration test, and more. For this, we can use Spock’s extension mechanism.
有时,我们可能需要修改或增强我们的规范的生命周期。例如,我们想添加一些条件执行,在随机失败的集成测试中重试,等等。为此,我们可以使用Spock的扩展机制。
Spock has a wide range of various extensions that we can hook onto a specification’s lifecycle.
Spock有广泛的各种扩展,我们可以将其挂在规范的生命周期中。
Let’s discover how to use the most common extensions.
让我们发现如何使用最常见的扩展。
2. Maven Dependencies
2.Maven的依赖性
Before we start, let’s set up our Maven dependencies:
在开始之前,我们先设置好Maven依赖项。
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>z
<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>
3. Annotation-Based Extensions
3.基于注释的扩展
Most of Spock‘s built-in extensions are based on annotations.
大多数Spock的内置扩展都是基于注释的。
We can add annotations on a spec class or feature to trigger a specific behavior.
我们可以在规范类或功能上添加注解,以触发特定的行为。
3.1. @Ignore
3.1. @Ignore
Sometimes we need to ignore some feature methods or spec classes. Like, we might need to merge our changes as soon as possible, but continuous integration still fails. We can ignore some specs and still make a successful merge.
有时我们需要忽略一些特性方法或规范类。比如,我们可能需要尽快合并我们的修改,但持续集成仍然失败。我们可以忽略一些规范,仍然可以进行成功的合并。
We can use @Ignore on a method level to skip a single specification method:
我们可以在方法层面上使用@Ignore来跳过单一的规范方法:。
@Ignore
def "I won't be executed"() {
expect:
true
}
Spock won’t execute this test method. And most IDEs will mark the test as skipped.
Spock不会执行这个测试方法。而且大多数IDE会把这个测试标记为skipped。
Additionally, we can use @Ignore on the class level:
此外,我们可以在类的层面上使用@Ignore。
@Ignore
class IgnoreTest extends Specification
We can simply provide a reason why our test suite or method is ignored:
我们可以简单地提供一个理由我们的测试套件或方法被忽略。
@Ignore("probably no longer needed")
3.2. @IgnoreRest
3.2. @IgnoreRest
Likewise, we can ignore all specifications except one, which we can mark with a @IgnoreRest annotation:
同样地,我们可以忽略所有的规范,除了一个,我们可以用@IgnoreRest注释来标记它。
def "I won't run"() { }
@IgnoreRest
def 'I will run'() { }
def "I won't run too"() { }
3.3. @IgnoreIf
3.3. @IgnoreIf
Sometimes, we’d like to conditionally ignore a test or two. In that case, we can use @IgnoreIf, which accepts a predicate as an argument:
有时,我们想有条件地忽略一两个测试。在这种情况下,我们可以使用 @IgnoreIf,它接受一个谓词作为参数。
@IgnoreIf({System.getProperty("os.name").contains("windows")})
def "I won't run on windows"() { }
Spock provides the set of properties and helper classes, to make our predicates easier to read and write:
Spock提供了一系列的属性和辅助类,使我们的谓词更容易读和写。
- os – Information about the operating system (see spock.util.environment.OperatingSystem).
- jvm – the JVM’s information (see spock.util.environment.Jvm).
- sys – System’s properties in a map.
- env – Environment variables in a map.
We can re-write the previous example throughout the use of os property. Actually, it’s the spock.util.environment.OperatingSystem class with some useful methods, like for example isWindows():
我们可以在整个使用osproperty的过程中重写前面的例子。实际上,它是spock.util.environment.OperatingSystem类,有一些有用的方法,例如isWindows()。
@IgnoreIf({ os.isWindows() })
def "I'm using Spock helper classes to run only on windows"() {}
Note, that Spock uses System.getProperty(…) underhood. The main goal is to provide a clear interface, rather than defining complicated rules and conditions.
注意,斯波克使用System.getProperty(…) 底层。主要目标是提供一个清晰的接口,而不是定义复杂的规则和条件。。
Also, as in the previous examples, we can apply the @IgnoreIf annotation at the class level.
另外,和前面的例子一样,我们可以在类的层面上应用@IgnoreIf注解。
3.4. @Requires
3.4.@要求
Sometimes, it’s easier to invert our predicate logic from @IgnoreIf. In that case, we can use @Requires:
有时,从@IgnoreIf中反转我们的谓词逻辑会更容易。在这种情况下,我们可以使用@Requires。
@Requires({ System.getProperty("os.name").contains("windows") })
def "I will run only on Windows"()
So, while the @Requires makes this test run only if the OS is Windows, the @IgnoreIf, using the same predicate, makes the test run only if the OS is not Windows.
因此,虽然@Requires使这个测试只在操作系统是Windows时运行,@IgnoreIf,使用相同的谓词,使测试只在操作系统是非Windows时运行。
In general, it’s much better to say under which condition the test will execute, rather than when it gets ignored.
一般来说,说测试在什么条件下执行要好得多,而不是在什么时候被忽略。
3.5. @PendingFeature
3.5.@PendingFeature
In TDD, we write tests first. Then, we need to write a code to make these tests pass. In some cases, we will need to commit our tests before the feature is implemented.
在TDD中,我们首先编写测试。然后,我们需要编写代码来使这些测试通过。在某些情况下,我们需要在功能实现之前提交我们的测试。
This is a good use case for @PendingFeature:
这是一个很好的@PendingFeature的用例:。
@PendingFeature
def 'test for not implemented yet feature. Maybe in the future it will pass'()
There is one main difference between @Ignore and @PendingFeature. In @PedingFeature, tests are executed, but any failures are ignored.
@Ignore和@PendingFeature之间有一个主要区别。在@PedingFeature中,测试被执行,但任何失败都被忽略。
If a test marked with @PendingFeature ends without error, then it will be reported as a failure, to remind about removing annotation.
如果一个标有@PendingFeature的测试没有出错,那么它将被报告为失败,以提醒删除注释。
In this way, we can initially ignore fails of not implemented features, but in the future, these specs will become a part of normal tests, instead of being ignored forever.
通过这种方式,我们最初可以忽略未实现功能的失败,但在未来,这些规格将成为正常测试的一部分,而不是永远被忽略。
3.6. @Stepwise
3.6.@Stepwise
We can execute a spec’s methods in a given order with the @Stepwise annotation:
我们可以用@Stepwise注解按照给定的顺序执行一个规范的方法。
def 'I will run as first'() { }
def 'I will run as second'() { }
In general, tests should be deterministic. One should not depend on another. That’s why we should avoid using @Stepwise annotation.
一般来说,测试应该是确定性的。一个不应该依赖于另一个。这就是为什么我们应该避免使用@Stepwise 注解。
But if we have to, we need to be aware that @Stepwise doesn’t override the behavior of @Ignore, @IgnoreRest, or @IgnoreIf. We should be careful with combining these annotations with @Stepwise.
但是如果我们不得不这样做,我们需要注意@Stepwise并不能覆盖@Ignore、@IgnoreRest或者@IgnoreIf的行为。我们应该小心地将这些注解与@Stepwise相结合。
3.7. @Timeout
3.7. @Timeout
We can limit the execution time of a spec’s single method and fail earlier:
我们可以限制一个规范的单一方法的执行时间,并提前失败:。
@Timeout(1)
def 'I have one second to finish'() { }
Note, that this is the timeout for a single iteration, not counting the time spent in fixture methods.
注意,这是单次迭代的超时,不包括在固定方法中花费的时间。
By default, the spock.lang.Timeout uses seconds as a base time unit. But, we can specify other time units:
默认情况下,spock.lang.Timeout使用秒作为基本时间单位。但是,我们可以指定其他的时间单位:。
@Timeout(value = 200, unit = TimeUnit.SECONDS)
def 'I will fail after 200 millis'() { }
@Timeout on the class level has the same effect as applying it to every feature method separately:
@Timeout在类层面上的效果与单独应用于每个特征方法的效果相同。
@Timeout(5)
class ExampleTest extends Specification {
@Timeout(1)
def 'I have one second to finish'() {
}
def 'I will have 5 seconds timeout'() {}
}
Using @Timeout on a single spec method always overrides class level.
在一个单一的规范方法上使用@Timeout总是覆盖类的水平。
3.8. @Retry
3.8.@Retry
Sometimes, we can have some non-deterministic integration tests. These may fail in some runs for reasons such as async processing or depending on other HTTP clients response. Moreover, the remote server with build and CI will fail and force us to run the tests and build again.
有时,我们会有一些非确定性的集成测试。这些测试在某些运行中可能会失败,原因包括异步处理或取决于其他HTTP客户端的响应。此外,带有构建和CI的远程服务器会失败,迫使我们重新运行测试和构建。
To avoid this situation, we can use @Retry annotation on a method or class level, to repeat failed tests:
为了避免这种情况,我们可以在方法或类的层面上使用@Retryannotation,来重复失败的测试。
@Retry
def 'I will retry three times'() { }
By default, it will retry three times.
默认情况下,它将重试三次。
It’s very useful to determine the conditions, under which we should retry our test. We can specify the list of exceptions:
这对于确定在什么条件下我们应该重试我们的测试非常有用。我们可以指定例外情况的列表。
@Retry(exceptions = [RuntimeException])
def 'I will retry only on RuntimeException'() { }
Or when there is a specific exception message:
或者当有一个特定的异常信息时。
@Retry(condition = { failure.message.contains('error') })
def 'I will retry with a specific message'() { }
Very useful is a retry with a delay:
非常有用的是有延迟的重试。
@Retry(delay = 1000)
def 'I will retry after 1000 millis'() { }
And finally, like almost always, we can specify retry on the class level:
最后,就像几乎总是这样,我们可以在类的层面上指定重试。
@Retry
class RetryTest extends Specification
3.9. @RestoreSystemProperties
3.9.@RestoreSystemProperties
We can manipulate environment variables with @RestoreSystemProperties.
我们可以用@RestoreSystemProperties操纵环境变量。
This annotation, when applied, saves the current state of variables and restores them afterward. It also includes setup or cleanup methods:
这个注解在应用时,会保存变量的当前状态并在之后恢复它们。它还包括setup或cleanup方法。
@RestoreSystemProperties
def 'all environment variables will be saved before execution and restored after tests'() {
given:
System.setProperty('os.name', 'Mac OS')
}
Please note that we shouldn’t run the tests concurrently when we’re manipulating the system properties. Our tests might be non-deterministic.
请注意,我们在操作系统属性时,不应该同时运行测试。我们的测试可能是非确定性的。
3.10. Human-Friendly Titles
3.10.对人友好的标题
We can add a human-friendly test title by using the @Title annotation:
我们可以通过使用@Title注解来添加一个人性化的测试标题。
@Title("This title is easy to read for humans")
class CustomTitleTest extends Specification
Similarly, we can add a description of the spec with @Narrative annotation and with a multi-line Groovy String:
同样地,我们可以用@Narrative注释和多行Groovy String来添加对规范的描述。
@Narrative("""
as a user
i want to save favourite items
and then get the list of them
""")
class NarrativeDescriptionTest extends Specification
3.11. @See
3.11. @See
To link one or more external references, we can use the @See annotation:
为了链接一个或多个外部引用,我们可以使用 @See注解。
@See("https://example.org")
def 'Look at the reference'()
To pass more than one link, we can use the Groovy [] operand for creating a list:
为了传递一个以上的链接,我们可以使用Groovy的[]操作数来创建一个列表。
@See(["https://example.org/first", "https://example.org/first"])
def 'Look at the references'()
3.12. @Issue
3.12.@Issue
We can denote that a feature method refers to an issue or multiple issues:
我们可以表示,一个特征方法是指一个问题或多个问题。
@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
3.13.@Subject
And finally, we can indicate which class is the class under test with @Subject:
最后,我们可以用@Subject表示哪个类是被测试的类。
@Subject
ItemService itemService // initialization here...
Right now, it’s only for informational purposes.
现在,它只是为了提供信息。
4. Configuring Extensions
4.配置扩展功能
We can configure some of the extensions in the Spock configuration file. This includes describing how each extension should behave.
我们可以在Spock配置文件中配置一些扩展。这包括描述每个扩展应该如何行事。
Usually, we create a configuration file in Groovy, called, for example, SpockConfig.groovy.
通常,我们在Groovy中创建一个配置文件,称为,例如,SpockConfig.groovy。
Of course, Spock needs to find our config file. First of all, it reads a custom location from the spock.configuration system property and then tries to find the file in the classpath. When not found, it goes to a location in the file system. If it’s still not found, then it looks for SpockConfig.groovy in the test execution classpath.
当然,Spock需要找到我们的配置文件。首先,它从spock.configuration系统属性中读取一个自定义位置,然后尝试在classpath中找到该文件。如果没有找到,它就去找文件系统中的一个位置。如果还是没有找到,那么它就在测试执行的classpath中寻找SpockConfig.groovy。
Eventually, Spock goes to a Spock user home, which is just a directory .spock within our home directory. We can change this directory by setting system property called spock.user.home or by an environment variable SPOCK_USER_HOME.
最终,Spock会进入Spock用户主页,这只是我们的主目录中的一个目录.spock。我们可以通过设置名为spock.user.home的系统属性或环境变量SPOCK_USER_HOME.来改变这个目录。
For our examples, we’ll create a file SpockConfig.groovy and put it on the classpath (src/test/resources/SpockConfig.Groovy).
对于我们的例子,我们将创建一个文件SpockConfig.groovy并把它放在classpath(src/test/resources/SpockConfig.Groovy)。
4.1. Filtering the Stack Trace
4.1.筛选堆栈跟踪
By using a configuration file, we can filter (or not) the stack traces:
通过使用一个配置文件,我们可以过滤(或不过滤)堆栈痕迹。
runner {
filterStackTrace false
}
The default value is true.
默认值为true.。
To see how it works and practice, let’s create a simple test which throws a RuntimeException:
为了看看它是如何工作和实践的,让我们创建一个简单的测试,抛出一个RuntimeException:。
def 'stacktrace'() {
expect:
throw new RuntimeException("blabla")
}
When filterStackTrace is set to false, then we’ll see in the output:
当filterStackTrace被设置为false时,那么我们将在输出中看到。
java.lang.RuntimeException: blabla
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
// 34 more lines in the stack trace...
By setting this property to true, we’ll get:
通过将此属性设置为true,我们将得到。
java.lang.RuntimeException: blabla
at extensions.StackTraceTest.stacktrace(StackTraceTest.groovy:10)
Although keep in mind, sometimes it’s useful to see the full stack trace.
尽管请记住,有时看到完整的堆栈跟踪是很有用的。。
4.2. Conditional Features in Spock Configuration File
4.2.Spock配置文件中的条件性功能
Sometimes, we might need to filter stack traces conditionally. For example, we’ll need to see full stack traces in a Continuous Integration tool, but this isn’t necessary on our local machine.
有时,我们可能需要有条件地过滤堆栈跟踪。例如,我们需要在持续集成工具中看到完整的堆栈跟踪,但这在我们的本地机器上是不必要的。
We can add a simple condition, based for example on the environment variables:
我们可以添加一个简单的条件,例如基于环境变量的条件。
if (System.getenv("FILTER_STACKTRACE") == null) {
filterStackTrace false
}
The Spock configuration file is a Groovy file, so it can contain snippets of Groovy code.
Spock配置文件是一个Groovy文件,所以它可以包含Groovy代码的片段。
4.3. Prefix and URL in @Issue
4.3.@Issue中的前缀和URL
Previously, we talked about the @Issue annotation. We can also configure this using the configuration file, by defining a common URL part with issueUrlPrefix.
之前,我们谈到了@Issue注解。我们也可以使用配置文件进行配置,通过用issueUrlPrefix定义一个通用的URL部分。
The other property is issueNamePrefix. Then, every @Issue value is preceded by the issueNamePrefix property.
另一个属性是issueNamePrefix。然后,每个@Issue值前面都有issueNamePrefix属性。
We need to add these two properties in the report:
我们需要在报告中添加这两个属性。
report {
issueNamePrefix 'Bug '
issueUrlPrefix 'https://jira.org/issues/'
}
4.4. Optimize Run Order
4.4.优化运行顺序
The other very helpful tool is optimizeRunOrder. Spock can remember which specs failed and how often and how much time it needs to execute a feature method.
另一个非常有用的工具是optimizeRunOrder。Spock可以记住哪些规范失败了,以及执行一个特征方法的频率和所需的时间。
Based on this knowledge, Spock will first run the features which failed in the last run. In the first place, it will execute the specs which failed more successively. Furthermore, the fastest specs will run first.
基于这些知识,Spock将首先运行上次运行失败的功能。首先,它将依次执行失败较多的规格。此外,最快的规格将首先运行。
This behavior may be enabled in the configuration file. To enable optimizer, we use optimizeRunOrder property:
这种行为可以在配置文件中启用。为了启用优化器,我们使用optimizeRunOrder属性。
runner {
optimizeRunOrder true
}
By default, the optimizer for run order is disabled.
默认情况下,运行顺序的优化器被禁用。
4.5. Including and Excluding Specifications
4.5.包括和不包括规格
Spock can exclude or include certain specs. We can lean on classes, super-classes, interfaces or annotations, which are applied on specification classes. The library can be of capable excluding or including single features, based on the annotation on a feature level.
Spock可以排除或包括某些规范。我们可以依靠类、超类、接口或注解,这些注解应用于规范类。该库可以根据特征层面的注解,有能力排除或包括单个特征。
We can simply exclude a test suite from class TimeoutTest by using the exclude property:
我们可以通过使用exclude属性简单地将一个测试套件从类TimeoutTest中排除。
import extensions.TimeoutTest
runner {
exclude TimeoutTest
}
TimeoutTest and all its subclasses will be excluded. If TimeoutTest was an annotation applied on a spec’s class, then this spec would be excluded.
TimeoutTest及其所有子类将被排除。如果TimeoutTest是应用于规范类的注解,那么这个规范将被排除。
We can specify annotations and base classes separately:
我们可以分别指定注解和基类。
import extensions.TimeoutTest
import spock.lang.Issue
exclude {
baseClass TimeoutTest
annotation Issue
}
The above example will exclude test classes or methods with the @Issue annotation as well as TimeoutTest or any of its subclasses.
上述例子将排除带有@Issue注解的测试类或方法,以及TimeoutTest或其任何子类。
To include any spec, we simply use include property. We can define the rules of include in the same way as exclude.
要包括任何规范,我们只需使用include属性。我们可以用与exclude相同的方式定义include的规则。
4.6. Creating a Report
4.6.创建一个报告
Based on the test results and previously known annotations, we can generate a report with Spock. Additionally, this report will contain things like @Title, @See, @Issue, and @Narrative values.
基于测试结果和之前已知的注释,我们可以用Spock.生成一份报告。此外,这份报告将包含诸如@Title, @See, @Issue, 和@Narrative值。
We can enable generating a report in the configuration file. By default, it won’t generate the report.
我们可以在配置文件中启用生成报告。默认情况下,它不会生成报告。
All we have to do is pass values for a few properties:
我们所要做的就是为几个属性传递数值。
report {
enabled true
logFileDir '.'
logFileName 'report.json'
logFileSuffix new Date().format('yyyy-MM-dd')
}
The properties above are:
上面的属性是。
- enabled – should or not generate the report
- logFileDir – directory of report
- logFileName – the name of the report
- logFileSuffix – a suffix for every generated report basename separated with a dash
When we set enabled to true, then it’s mandatory to set logFileDir and logFileName properties. The logFileSuffix is optional.
当我们将enabled设置为true,时,就必须设置logFileDir和logFileName属性。logFileSuffix是可选的。
We can also set all of them in system properties: enabled, spock.logFileDir, spock.logFileName and spock.logFileSuffix.
我们也可以在系统属性中设置所有这些内容。enabled, spock.logFileDir, spock.logFileName 和 spock.logFileSuffix.。
5. Conclusion
5.总结
In this article, we described the most common Spock extensions.
在这篇文章中,我们描述了最常见的Spock扩展。
We know that most of them are based on annotations. In addition, we learned how to create a Spock configuration file, and what the available configuration options are. In short, our newly acquired knowledge is very helpful for writing effective and easy to read tests.
我们知道,它们中的大多数都是基于注释的。此外,我们学会了如何创建Spock配置文件,以及可用的配置选项是什么。简而言之,我们新获得的知识对于编写有效的、易于阅读的测试非常有帮助。
The implementation of all our examples can be found in our Github project.
我们所有例子的实现都可以在我们的Github项目中找到。