Spock 测试框架

Spock 测试框架

1. Spock Primer

todo

2. 数据驱动测试

todo

3. 基于交互的测试

class Publisher {
  List subscribers = []
  void send(String message){
    subscribers*.receive(message)
  }
}

interface Subscriber {
  void receive(String message)
}

class PublisherSpec extends Specification {
  Publisher publisher = new Publisher()
}

mock 框架提供了一种方式, 可以描述 spec 对象(即待测对象)及其协作者之间的期望交互,
并且能生成协作者的 mock 实现, 用来核查这个期望.

要测试 send 方法, 得有 Subscriber 类型的协作者.

3.1 创建 mock 对象

创建两个 mock 对象:

def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
// 用下面的方式也是可以的, ide 自动提示功能可能会更好:
// mock 对象的类型会根据变量类型推断出来
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

3.2 把 mock 对象注入到待测对象

class PublisherSpec extends Specification {
  Publisher publisher = new Publisher()
  Subscriber subscriber = Mock()
  Subscriber subscriber2 = Mock()

  def setup() {
    publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()
    publisher.subscribers << subscriber2
  }

3.3 Mocking

3.3.1 Interactions

then: block 中, 有两个 interactions, 每个都由 cardinality, target, method, argument
组成.

1 * subscriber.receive("hello")
|   |          |       |
|   |          |       argument constraint
|   |          method constraint
|   target constraint
cardinality

3.3.2 Cardinality(基数)

一个交互(interaction) 的基数描述了期望一个方法调用几次. 它可以是一个固定数字, 也
可以是范围.


1 * subscriber.receive("hello")      // 精确的 1 次调用
0 * subscriber.receive("hello")      // 0 次
(1..3) * subscriber.receive("hello") // 1 到 3 次 (inclusive)
(1.._) * subscriber.receive("hello") // 至少 1 次
(_..3) * subscriber.receive("hello") // 最多 3 次
_ * subscriber.receive("hello")      // any number of calls, including zero
                                     // (rarely needed; see 'Strict Mocking')

3.3.3 Target Constraint

Target constraint 描述了期望哪个 mock 对象来接收方法调用.


1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello")          // a call to any mock object

3.3.4 Method Constraint

Method constraint 描述了期望哪个方法被调用


1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello")  // a method whose name matches the given regular expression

当期望一个 getter 方法调用时, 可以用 groovy 的属性访问语法.


1 * subscriber.status // same as: 1 * subscriber.getStatus()

当期望一个 setter 方法调用时, 只能使用方法语法.


1 * subscriber.setStatus("ok") // NOT: 1 * subscriber.status = "ok"

3.3.5 Argument Constraints

描述了方法期望的参数.


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)

当然, 多个参数也是可以的.

处理可变参数时, 跟平时一样用就可以了.

interface VarArgSubscriber {
    void receive(String... messages)
}

...

subscriber.receive("hello", "goodbye")

3.3.6 匹配任意方法调用


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

3.3.7 Strick Mocking(严格模式)

3.3.8 Where to Declare Interactions

Interactions 不只是可以在 then: block 中出现, 也可以在 setup 方法中出现.

3.3.9 Declaring Interactions at Mock Creation Time (New in 0.7)

如果一个 mock 对象有一组基本的不变的 interactions. 这些 interactions 可以在 mock
对象创建时就声明.

def subscriber = Mock(Subscriber) {
    1 * receive("hello")
    1 * receive("goodbye")
}

// 也可以这样
Subscriber subscriber = Mock {
    1 * receive("hello")
    1 * receive("goodbye")
}

这个特性对 带有专门的 Stubs 的 Stubbing 很有用. 注意上面的 interactions 没有 target
contraints, 因为从上下文中可以清楚的知道他们属于哪个 mock 对象.

3.3.10 Grouping Interactions with Same Target (New in 0.7)

with(subscriber) {
    1 * receive("hello")
    1 * receive("goodbye")
}

一个 with block 还可以用在 grouping conditions.

3.3.11 Mixing Interactions and Conditions

一个 then: block 可以同时包含 interactions 和 conditions. 尽管不是严格必须, 但是
通常的做法是把 interactions 的声明放在 conditions 前面.


when:
publisher.send("hello")

then:
1 * subscriber.receive("hello")
publisher.messageCount == 1

3.3.12 显式定义 interaction 代码块

Internally, Spock must have full information about expected interactions before they take place. So how is it possible for interactions to be declared in a then: block? The answer is that under the hood, Spock moves interactions declared in a then: block to immediately before the preceding when: block. In most cases this works out just fine, but sometimes it can lead to problems:

这样做会出问题

when:
publisher.send("hello")

then:
def message = "hello"
1 * subscriber.receive(message)

Spock 不知道这个交互链接到一个变量声明. 只会把交互这一条语句移到 when: 代码块里去.
所以会产生 MissingPropertyException 异常.

一个解决办法是把 message 声明移到 when: 代码块之前(或者放在 where 代码块中).

另一个解决办法是显式的声明 interaction 代码块

when:
publisher.send("hello")

then:
interaction {
    def message = "hello"
    1 * subscriber.receive(message)
}

3.3.13 Scope of Interactions

3.3.14 Verification of Interactions

主要是基数不匹配

3.3.15 Invocation Order

在一个 then: block 中, invocation 的顺序是不重要的.

例如:

then:
2 * subscriber.receive("hello")
1 * subscriber.receive("goodbye")

可以匹配 "hello" "hello" "goodbye", "hello" "goodbye" "hello", 都没问题.

如果要严格限定顺序的情况, 可以写在两个 then: block 中.

then:
2 * subscriber.receive("hello")

then:
1 * subscriber.receive("goodbye")

3.3.16 Mocking Classes

除了接口, spock 还支持类的 mock. 基本与接口的 mock 是一致的, 除了需要额外的 cglib
库支持. cglib-nodep-2.2 or higher and objenesis-1.2 or higher.

如果 classpath 中没有, 会报错哦. Spock 会告诉你这种错误信息的.

org.spockframework.mock.CannotCreateMockException: Cannot create mock for class cn.codergege.demo.mock.NewSubcriber. Mocking of non-interface types requires a code generation library. Please put byte-buddy-1.4.0 or cglib-nodep-3.2 or higher on the class path.

注意: java 8 只支持 CGLIB 3.2.0 及更高版本.

3.4 Stubbing

todo

3.5 Combining Mocking and Stubbing


1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"

当 mocking 和 stubbing 同一个方法时, 他们必须出现在同一个 interaction 中. 下面这
种 Mockito 风格的写法是不能工作的(分开 stubbing 和 mocking 到不同的语句中)


setup:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")

这个 receive 方法将在 then: 代码块中第一次匹配. 因为此时没有指定返回值, 所以默认
的方法返回值被返回(这里会返回 null). 因此, 在 setup: 代码块中的 interaction 永远
没有匹配的机会.

你可能感兴趣的:(Spock 测试框架)