推荐大家在写单侧的时候使用spock框架, 使用groovy语言进行编写单侧用例的时候, 要比JUnit简单, 而且方便的多, 会节省大家很多的代码.

官网学习地址:http://spockframework.org/spock/docs/1.3/index.html

1. 介绍


我们来看一下官网的介绍,
单元测试(三)spock框架_第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)