美文网首页
Spock 测试框架

Spock 测试框架

作者: codergege | 来源:发表于2017-03-01 14:00 被阅读0次

    Spock 测试框架

    1. Spock Primer

    todo

    2. 数据驱动测试

    todo

    3. 基于交互的测试

    class Publisher {
      List<Subscriber> 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 测试框架

          本文链接:https://www.haomeiwen.com/subject/ddgqgttx.html