在上一次Spock实践中我们介绍了Spock的文档化测试和HTTP接口测试实践,今天我们用Spock做一些mock的实践。
对于测试来说,除了能够对输入-输出进行验证之外,还希望能验证模块与其他模块之间的交互是否正确,比如“是否正确调用了某个对象中的函数”;或者期望被调用的模块有某个返回值,等等。各类mock框架让这类验证变得可行,而spock除了支持这类验证,并且做的更加优雅,下面我们看一下在Spock里mock的应用实践。
一、mock
首先我们在Spock中创建一个mock对象:
class PublisherSpec extends Specification {
Publisher publisher = new Publisher()
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()
def setup() {
publisher.subscribers.add(subscriber)
publisher.subscribers.add(subscriber2)
}
}
而创建了mock对象之后就可以对它的交互做验证了:
def "should send messages to all subscribers" () {
when:
publisher.send( "hello" )
then:
1 * subscriber.receive( "hello" )
1 * subscriber2.receive( "hello" )
}
上面的例子里验证了:在publisher调用send时,两个subscriber都应该被调用一次receive(“hello”)。示例中,表达式中的次数、对象、函数和参数部分都可以灵活定义:
1 * subscriber.receive( "hello" ) // exactly one call
0 * subscriber.receive( "hello" ) // zero calls
( 1 .. 3 ) * subscriber.receive( "hello" ) // between one and three calls (inclusive)
( 1 .._) * subscriber.receive( "hello" ) // at least one call
(_.. 3 ) * subscriber.receive( "hello" ) // at most three calls
_ * subscriber.receive( "hello" ) // any number of calls, including zero
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({ it.size() > 3 }) // an argument that satisfies the given predicate
// (here: message length is greater than 3)
1 * subscriber._(*_) // any method on subscriber, with any argument list
1 * subscriber._ // shortcut for and preferred over the above
1 * _._ // any method call on any mock object
1 * _ // shortcut for and preferred over the above
得益于groovy脚本语言的特性,在定义交互的时候不需要对每个参数指定类型,如果用过java下的其它mock框架应该会被这个特性深深的吸引住。
二、Stubbing
对mock对象定义函数的返回值可以用如下方法:
subscriber.receive(_) >> "ok"
符号代表函数的返回值,执行上面的代码后,再调用subscriber.receice方法将返回ok。如果要每次调用返回不同结果,可以使用:
subscriber.receive(_) >>> [ "ok" , "error" , "error" , "ok" ]
如果要做额外的操作,如抛出异常,可以使用:
subscriber.receive(_) >> { throw new InternalError( "ouch" ) }
而如果要每次调用都有不同的结果,可以把多次的返回连接起来:
subscriber.receive(_) >>> [ "ok" , "fail" , "ok" ] >> { throw new InternalError() } >> "ok"
三、mock and stubbing
如果既要判断某个mock对象的交互,又希望它返回值的话,可以结合mock和stub,可以这样:
then:
1 * subscriber.receive( "message1" ) >> "ok"
1 * subscriber.receive( "message2" ) >> "fail"
注意,spock不支持两次分别设定调用和返回值,如果把上例写成这样是错的:
setup:
subscriber.receive( "message1" ) >> "ok"
when:
publisher.send( "message1" )
then:
1 * subscriber.receive( "message1" )
此时spock会对subscriber执行两次设定:
四、其它类型的mock对象
Spock也支持spy、stub之类的mock对象,但是并不推荐使用,因为使用“正规的”BDD思路写出的代码不需要用这些方法来测试,官方的解释是:
Think twice before using this feature. It might be better to change the design of the code under specification.
(在使用此功能之前请三思。在规范下更改代码的设计可能会更好。)
具体的使用方法如果有兴趣可以参考官方文档.