推荐大家在写单侧的时候使用spock框架, 使用groovy语言进行编写单侧用例的时候, 要比JUnit简单, 而且方便的多, 会节省大家很多的代码.
官网学习地址:http://spockframework.org/spock/docs/1.3/index.html
1. 介绍
2. spock 语法
2.1 导入包
import spock.lang.*
2.2Specification
所有的测试类, 都必须统一继承Specification类
class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}
2.3 Fields(字段, 属性)
def obj = new ClassUnderSpecification()
def coll = new Collaborator()
static final PI = 3.141592654
2.4 Fixture Methods
预先定义的几个固定的函数,与junit或testng中类似,不多解释了
def setupSpec() {} // runs once - before the first feature method
def setup() {} // runs before every feature method
def cleanup() {} // runs after every feature method
def cleanupSpec() {} // runs once - after the last feature method
2.5 Feature Methods(单侧函数)
在写单侧的时候, 我们都需要定义函数, 以下是定义函数的示例:
def "pushing an element on the stack"() {
// blocks go here
}
2.6 Blocks
每个feture method 又被划分为不同的block, 不同的block处于测试执行的不同阶段, 在各个block 按照不同顺序和规则执行
2.6.1 setup blocks
在这个block中会放置测试函数初始化程序
setup:
def stack = new Stack()
def elem = "push me"
2.6.2 given Blocks
given: 给定一个前提条件
given:
def stack = new Stack()
def elem = "push me"
2.6.3 when and then blocks
语法:
when: // stimulus, 当执行改方法的时候,
then: // response, 类似于我们的断言
示例:
given:
def stack = new Stack()
when:
stack.push(elem)
then:
!stack.empty
stack.size() == 1
stack.peek() == elem
2.6.4 Expect Blocks
expect:
Math.max(1, 2) == 2
相当于
when:
def x = Math.max(1, 2)
then:
x == 2
2.6.5 cleanup blocks
函数退出前做一些清理工作,如关闭资源等。
given:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()
2.6.6 where blocks
做测试时最复杂的事情之一就是准备测试数据,尤其是要测试边界条件、测试异常分支等,这些都需要在测试之前规划好数据。但是传统的测试框架很难轻松的制造数据,要么依赖反复调用,要么用xml或者data provider函数之类难以理解和阅读的方式。比如说:
class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
// exercise math method for a few different inputs
Math.max(1, 3) == 3
Math.max(7, 4) == 7
Math.max(0, 0) == 0
}
}
而在spock中,通过where block可以让这类需求实现起来变得非常优雅:
class DataDriven extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
3 | 5 || 5
7 | 0 || 7
0 | 0 || 0
}
}
上述例子实际会跑三次测试,相当于在for循环中执行三次测试,a/b/c的值分别为3/5/5,7/0/7和0/0/0。如果在方法前声明@Unroll,则会当成三个方法运行。
更进一步,可以为标记@Unroll的方法声明动态的spec名:
class DataDriven extends Specification {
@Unroll
def "maximum of #a and #b should be #c"() {
expect:
Math.max(a, b) == c
where:
a | b || c
3 | 5 || 5
7 | 0 || 7
0 | 0 || 0
}
}
运行时,名称会被替换为实际的参数值。
除此之外,where block还有两种数据定义的方法,并且可以结合使用,如:
# a相当于 3 7 0 其他是占位符
where:
a | _
3 | _
7 | _
0 | _
# b相当于5 0 0 这三个值
b << [5, 0, 0]
c = a > b ? a : b
2.7 断言
2.7.1 == 断言
when:
stack.push(elem)
then:
!stack.empty
stack.size() == 1
stack.peek() == elem
2.7.2 关键字assert断言
def setup() {
stack = new Stack()
assert stack.empty
}
2.7.3 异常断言
方式一:
when:
stack.pop()
then:
thrown(EmptyStackException)
stack.empty
2.8 交互测试(Interaction)
对于测试来说,除了能够对输入-输出进行验证之外,还希望能验证模块与其他模块之间的交互是否正确,比如“是否正确调用了某个某个对象中的函数”;或者期望被调用的模块有某个返回值,等等。
各类mock框架让这类验证变得可行,而spock除了支持这类验证,并且做的更加优雅。如果你还不清楚mock是什么,最好先去简单了解一下,网上的资料非常多,这里就不展开了。
2.8.1 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 0次
(1..3) * subscriber.receive("hello") // between one and three calls (inclusive) 1-3次
(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框架应该会被这个特性深深的吸引住。
2.8.2 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"
2.9 spock对比JUnit
Spock JUnit
Specification Test class
setup() @Before
cleanup() @After
setupSpec() @BeforeClass
cleanupSpec() @AfterClass
Feature Test
Feature method Test method
Data-driven feature Theory
Condition Assertion
Exception condition @Test(expected=…)
Interaction Mock expectation (e.g. in Mockito)