一、Spock 是什么
简单地说,spock
是一个测试框架,他的核心特性有以下几个:
【1】可以应用于java
或groovy
应用的单元测试框架。
【2】测试代码使用基于groovy
语言扩展而成的规范说明语言specification language
。
【3】通过junit runner
调用测试,兼容绝大部分junit
的运行场景(ide
,构建工具,持续集成等)。
【4】框架的设计思路参考了JUnit
,jMock
,RSpec
,Groovy
,Scala,Vulcans……
Groovy
是Java
平台上设计的面向对象编程语言。这门动态语言拥有类似Python
、Ruby
和Smalltalk
中的一些特性,可以作为Java
平台的脚本语言使用。
Groovy
的语法与Java非常相似,以至于多数的Java
代码也是正确的Groovy
代码。Groovy
代码动态的被编译器转换成Java
字节码。由于其运行在JVM
上的特性,Groovy
可以使用其他Java
语言编写的库。
【1】轻量级、对于java
程序员来说门槛低
【2】完全兼容java
语法
【3】提供脚本语言灵活性、元编程、specification language
二、为什么使用 Spock
【1】spock
框架使用标签分隔单元测试中不同的代码,更加规范,也符合实际写单元测试的思路
【2】代码写起来更简洁、优雅、易于理解
【3】由于使用groovy
语言,所以也可以享受到脚本语言带来的便利
【4】底层基于jUnit
,不需要额外的运行框架
【5】已经发布了2.0-M
版本,基本没有比较严重的bug
def "test getTransportTypeNameConfig"(){
given:
enumMapConfig.put("name.type.transport:"+transportType,expecyName);
when:
String actualTypeName = settingContext.getTransportTypeNameConfig(transportType);
then:
expecyName == actualTypeName;
where:
transportType | expecyName
"A" | "AType"
"B" | "BType"
null | ""
}
三、使用 Spock
【1】maven
引入spock
框架
<!-- Mandatory dependencies for using Spock -->
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<scope>test</scope>
</dependency>
【2】 Groovy
语言支持
<dependency> <!-- use a specific Groovy version rather than the one specified by spock-core -->
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy</artifactId>
<version>2.5.9</version>
</dependency>
由于spock
是基于groovy
语言的,所以需要创建groovy
的测试源码目录:首先在test
目录下创建名为groovy
的目录,之后将它设为测试源码目录
def "test getTransportTypeNameConfig"(){
given:
enumMapConfig.put("name.type.transport:"+transportType,expecyName);
when:
String actualTypeName = settingContext.getTransportTypeNameConfig(transportType);
then:
expecyName == actualTypeName;
where:
transportType | expecyName
"A" | "AType"
"B" | "BType"
null | ""
}
四、Spock 基本概念
Specification等同于 测试类,继承自spock.lang Specification
基类, Specification
是一个Groovy
类,提供一些有用的方法,verifyAll(Coloure)
、With
、Throw
还有Sputnik, Spock’s JUnit runner.
、Mock
之类的
class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}
Methods 夹具
def setupSpec() {} // runs once -before the first feature method
def setup() {} // runs beforeevery feature method
def cleanup() {} // runs afterevery feature method
def cleanupSpec() {} // runs once -after the last feature method
feature method这个是spock
测试代码的核心,类似于Junit
中的TestMethod
,支持使用 字符串字面量命名,这样可以得到更高的可读性。
Conceptually, a feature method consists of four phases:
1. Set up the feature's fixture
2. Provide a stimulus to the system under specification
3. Describe the response expected from the system
4. Clean up the feature's fixture
Blocks每个feature method
又被划分为不同的block
,不同的block
处于测试执行的不同阶段,在测试运行时,各个block
按照不同的顺序和规则被执行
Given Blocks 主要做初始化
given: def stack = new Stack()
def elem = "push me”
When and Then Blocks ,调用被测试逻辑,执行测试条件
Conditions
测试目标类
def "HashMap accepts null key"(){
given:
def map = new HashMap()
when:
map.put(null, "elem")
then:
notThrown(NullPointerException)
}
when:
stack.push(elem)
then:
!stack.empty
stack.size()==1
stack.peek()== elem
异常测试
GROOVY
when :
stack.pop()
then :
thrown(EmptyStackException)
stack.empty
Conditions 方法调用
有时候可能需要检查 依赖的方法背调用,调用、几次、参数是啥
def "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:
1 * subscriber1.receive("event")
1 * subscriber2.receive("event")
1 * subscriber.receive("hello")
// an argument that is equal to the String "hello"
1 * subscriber.receive(!"hello")
// an argument that is unequal to the String "hello"
1 * subscriber.receive()
// the empty argument list (would never match in our
example)
1 * subscriber.receive(_)
// any single argument (including null)
1 * subscriber.receive(*_)
// any argument list (including the empty argument list)
1 * subscriber. receive(!null)
// any non-null argument
1 * subscriber.receive(_ as String)
// any non-null argument that is-a String
1 * subscriber. receive(endsWith("lo")) // any non-null argument that is-a String
1 * subscriber.receive({ it.size()>3 &&it.contains('a')})
// an argument that satisfies the given predicate, meaning that
// code argument constraints need to return true of false
// depending on whether they match or not
// (here: message length is greater than 3 and contains the character a)
}
Conditions —— Expect BlockWhen
和Then
写到一起就是Expect
when:
def x= Math.max(1, 2)
then :
×==2
expect:
Math.max(1,2) == 2
given:
def file = new File("/some/path")
file.createNewFile()
//...
cleanup:
file.delete()
Where Blocks 与Data Driven Testing
写好测试逻辑,使用不同的数据测试,方便对边界检查等,还有一些技巧,可以发挥想象力
class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b)== c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
五、Spock Mocking
def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
subscriber.receive(_) >> “ok”
每次都返回“ok”
subscriber.receive(“message1”) >> “ok”
参数是message1
时返回OK
subscriber.receive(_) >>> [“ok”, “error”, “error”, “ok”]
返回序列值
subscriber.receive(_) >> { args -> args[0].size() > 3 ? “ok” : “fail” }
你想返回什么值
subscriber.receive(_) >> { throw new InternalError(“ouch”) }
异常
ubscriber.receive(_) >>> [“ok”, “fail”, “ok”] >> { throw new InternalError() } >> “ok”
组合起来
given:
subscriber.receive("message1")>"ok"
when :
publisher.send("message1")
then :
1 * subscriber.receive("message1")
Where Blocks 与Data Driven Testing
写好测试逻辑,使用不同的数据测试,方便对边界检查等,还有一些技巧,可以发挥想象力
class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b)== c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}