写给 Javaer 看的 Spock 快速入门
Spock 是什么?
看下它的 github 描述:
The Enterprise-ready testing and specification framework.
官网介绍:
Spock is a testing and specification framework for Java and Groovy applications.
What makes it stand out from the crowd is its beautiful and highly expressive specification language.
Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers.
Spock is inspired from JUnit, RSpec, jMock, Mockito, Groovy, Scala, Vulcans, and other fascinating life forms.
官方文档:Spock Framework Reference Documentation
简而言之,Spock 是一款测试框架,支持 Java 或者 Groovy 应用,是采用 groovy 语言写的,得益于 groovy 的语言特性,Spock 相比较 JUnit 语法更简练、提供更强大的功能。
和其他 Java 测试框架的对比
对比其他的 Java 测试框架,包括常见的 JUnit, testNG, 一些 Java BDD 框架, 例如 Cucumber(Gherkin),JBehave, Spock 有如下优势:
基于它提供的一套 DSL,能更好地实践 BDD —— 测试用例即需求说明书。 (
given-when-then
)写更少的测试代码,测试用例可读性更强,一些 awesome feature,后面会详细解释:
- blocks, (no)thrown, ‘==’,
- where, table, database,
- with, verifyAll
- 更灵活的 Mock/Stub 语法
兼容
JUnit
, 支持 JUnit 里的所有@Rule
,可以平滑地从 JUnit 迁移到 Spock
Spock vs JUnit
通过一个参数化测试的例子直观感受下:
Spock:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import spock.lang.Specification
import spock.lang.Unroll
"Testing file extension validation method") (
class ImageValidatorShould extends Specification {
def "validate extension of #fileToValidate"() {
when: "validator checks filename"
def isValid = validate fileToValidate
then: "return appropriate result"
isValid == expectedResult
where: "input files are"
fileToValidate || expectedResult
'some.jpeg' || true
'some.jpg' || true
'some.tiff' || false
'some.bmp' || true
'some.png' || false
}
}
JUnit:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import java.util.Collection;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
(Parameterized.class)
public class ImageValidator {
public static Collection<Object[]> data() {
return asList(new Object[][]{
{"some.jpeg", true},
{"some.jpg", true},
{"some.tiff", false},
{"some.bmp", true},
{"some.png", false}
});
}
private String file;
private boolean isValid;
public ImageValidator(String input, boolean expected) {
file = input;
isValid = expected;
}
public void validateFileExtension() {
assertEquals(isValid, validate(file));
}
}
上述例子摘自:link
基本用法
测试方法的生命周期
和 JUnit 基本类似,详见:对比 JUnit
e.g.:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def "events are published to all subscribers"() {
given: "准备数据"
def subscriber1 = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
def publisher = new Publisher()
publisher.add(subscriber1)
publisher.add(subscriber2)
when: "触发被测试对象的一个行为"
publisher.fire("event")
then: "验证该行为的结果是否符合预期"
// 验证 subscriber1 的 receive 方法是否被调用一次,且入参为 "event"
1 * subscriber1.receive("event")
1 * subscriber2.receive("event")
}
简洁的 Mock 语法
Spock 有自带的一套 Mock 框架,提供非常简洁直观的语法,从上面那个例子也能看出它的可读性很强,如果同样的代码用 Java Mock 框架,如 Mockito,来写的话,是这样:1
2Mockito.verify(subscriber1, Mockito.times(1)).sendMail("event");
Mockito.verify(subscriber2, Mockito.times(1)).sendMail("event");
隐式断言
Within the
then
andexpect
blocks, assertions are implicit
无需显示写 assert,语法简洁
1 | when: |
异常断言
thrown, notThrown
1
2
3
4
5
6when:
stack.pop()
then:
thrown(EmptyStackException)
stack.empty
各种 Groovy 的语法糖
Groovy 提供的很多语法糖帮助开发不需要写很多模板代码,使用 Spock 写的测试用例更简洁易读,得益于它提供的这套 DSL,整个测试用例的结构更清晰、看起来更接近自然语言。
e.g:
Java’s == is actually Groovy’s is() method, and Groovy’s == is a clever equals()!
1 | str == "content" // assert the equality between strings |
1 | publisher.subscribers << subscriber // << is a Groovy shorthand for List.add() |
参考:
groovy-lang.org/syntax.html
更多例子
支持 Java 的各种测试框架
包括 JUnit, Mockito, JAssert, Hamcrest, 等等
参数化测试
杀手级特性
Data Tables
1 | class MathSpec extends Specification { |
Data Pipes
1 | ... |
报告生成
given
when
then
等代码块里的描述是可以导出到文档中的:
测试用例是给开发人员看的文档,BDD 风格的测试用例可读性更强,帮忙阅读者更快地熟悉复杂的业务逻辑。
从 Java 迁移到 Groovy 的注意事项
java lambda expression vs groovy closure
java:
1 | () |
groovy:
1 | {} |
注解当中的:[]
vs{}
java:
1 | (classes = {A.class, B.class}) |
groovy:
1 | class, B.class]) (classes = [A. |
groovy 更便捷的 getter & setter
1 | class Person { |
落地实践
生产代码用 java 写,测试代码用 groovy,享受新语言优势的同时又无需承担线上风险。
e.g.
学习成本
- groovy 号称是 “Java 的脚本的语言”,Java 程序员学习 groovy 的成本较低。
- Spock 中文资料不是很多,直接阅读官方文档是最推荐的学习方式
兼容性
如果对 groovy 语法不是太熟悉的话,可以选择在 Spock 里写 Java 代码,groovy 兼容绝大多数的 java 语法。 e.g.:1
2
3
4
5
6
7
8
9// 各种 groovy 语法和 Java 语法的混用,
List list0 = new ArrayList()
List list = ['1', '2', '3']
list << '4'
list << "5"
list.add("6")
println(list[0])
println list.get(0)
System.out.println(list[0])
groovy 还兼容 JUnit (Spock 的底层是基于 Junit 引擎实现的)和 maven (因为 maven 构建只认 class 文件),因此也兼容 CI 平台。
不足之处
跨语言,有一定学习成本和推广难度
社区活跃度和 JUnit 还不是一个数量级,我之前在使用过程中也遇到过一些小问题,不过影响不大
习惯 Spock 之后容易厌弃 JUnit ……
分享一个之前在 spring-redis 源码里看到的彩蛋,这位老哥可能是写单测写太辛苦了,直接在代码注释里吐槽起了 JUnit,甚至还画了一个表情 (摊手)
maven 配置
pom.xml:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55</dependencies>
<!-- spock -->
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.athaydes</groupId>
<artifactId>spock-reports</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<goals>
<goal>compileTests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Test.java</include>
<include>**/*Spec.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!