Spock测试框架中Mock/Stub/Spy之间的区别

Q L*_*Liu 83 testing mocking stub spy spock

我不明白Spock测试中Mock,Stub和Spy之间的区别,我在网上看过的教程并没有详细解释它们.

kri*_*aex 84

注意:我将在即将到来的段落中过度简化,甚至可能会略微伪造.有关更多详细信息,请参阅Martin Fowler的网站.

mock是一个替换真实类的虚拟类,为每个方法调用返回null或0.如果您需要复杂类的虚拟实例,否则您将使用模拟,否则将使用外部资源,如网络连接,文件或数据库,或者可能使用许多其他对象.模拟的优点是可以将被测试的类与系统的其余部分隔离开来.

存根也是一个虚拟类,为某些被测请求提供一些更具体,准备或预先录制的重放结果.你可以说存根是一个花哨的模拟.在Spock中,您经常会阅读存根方法.

间谍是真实对象和存根之间的混合体,即它基本上是真实对象,其中一些(不是全部)方法被存根方法遮蔽.非存根方法只是路由到原始对象.通过这种方式,您可以拥有"廉价"或琐碎方法的原始行为以及"昂贵"或复杂方法的虚假行为.


更新2017-02-06:实际上,用户mikhail的回答比Spock更具体,而不是我原来的回答.因此,在Spock的范围内,他描述的内容是正确的,但这并不能篡改我的一般答案:

  • 存根涉及模拟特定行为.在Spock中,这是一个存根可以做的,所以它是最简单的事情.
  • 模拟涉及代表(可能是昂贵的)真实对象,为所有方法调用提供无操​​作答案.在这方面,模拟比存根更简单.但是在Spock中,模拟也可以存根方法结果,即同时是模拟和存根.此外,在Spock中,我们可以计算在测试期间调用具有某些参数的特定模拟方法的频率.
  • 间谍总是包装一个真实对象,默认情况下将所有方法调用路由到原始对象,同时传递原始结果.方法调用计数也适用于间谍.在Spock中,间谍还可以修改原始对象的行为,操纵方法调用参数和/或结果或阻止原始方法被调用.

现在这里是一个可执行的示例测试,展示了什么是可能的,什么是不可能的.它比mikhail的片段更有启发性.非常感谢他鼓励我改进自己的答案!:-)

package de.scrum_master.stackoverflow

import org.spockframework.mock.TooFewInvocationsError
import org.spockframework.runtime.InvalidSpecException
import spock.lang.FailsWith
import spock.lang.Specification

class MockStubSpyTest extends Specification {

  static class Publisher {
    List<Subscriber> subscribers = new ArrayList<>()

    void addSubscriber(Subscriber subscriber) {
      subscribers.add(subscriber)
    }

    void send(String message) {
      for (Subscriber subscriber : subscribers)
        subscriber.receive(message);
    }
  }

  static interface Subscriber {
    String receive(String message)
  }

  static class MySubscriber implements Subscriber {
    @Override
    String receive(String message) {
      if (message ==~ /[A-Za-z ]+/)
        return "ok"
      return "uh-oh"
    }
  }

  Subscriber realSubscriber1 = new MySubscriber()
  Subscriber realSubscriber2 = new MySubscriber()
  Publisher publisher = new Publisher(subscribers: [realSubscriber1, realSubscriber2])

  def "Real objects can be tested normally"() {
    expect:
    realSubscriber1.receive("Hello subscribers") == "ok"
    realSubscriber1.receive("Anyone there?") == "uh-oh"
  }

  @FailsWith(TooFewInvocationsError)
  def "Real objects cannot have interactions"() {
    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * realSubscriber1.receive(_)
  }

  def "Stubs can simulate behaviour"() {
    given:
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >>> ["hey", "ho"]
    }

    expect:
    stubSubscriber.receive("Hello subscribers") == "hey"
    stubSubscriber.receive("Anyone there?") == "ho"
    stubSubscriber.receive("What else?") == "ho"
  }

  @FailsWith(InvalidSpecException)
  def "Stubs cannot have interactions"() {
    given: "stubbed subscriber registered with publisher"
    def stubSubscriber = Stub(Subscriber) {
      receive(_) >> "hey"
    }
    publisher.addSubscriber(stubSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then:
    2 * stubSubscriber.receive(_)
  }

  def "Mocks can simulate behaviour and have interactions"() {
    given:
    def mockSubscriber = Mock(Subscriber) {
      3 * receive(_) >>> ["hey", "ho"]
    }
    publisher.addSubscriber(mockSubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("Hello subscribers")
    1 * mockSubscriber.receive("Anyone there?")

    and: "check behaviour exactly 3 times"
    mockSubscriber.receive("foo") == "hey"
    mockSubscriber.receive("bar") == "ho"
    mockSubscriber.receive("zot") == "ho"
  }

  def "Spies can have interactions"() {
    given:
    def spySubscriber = Spy(MySubscriber)
    publisher.addSubscriber(spySubscriber)

    when:
    publisher.send("Hello subscribers")
    publisher.send("Anyone there?")

    then: "check interactions"
    1 * spySubscriber.receive("Hello subscribers")
    1 * spySubscriber.receive("Anyone there?")

    and: "check behaviour for real object (a spy is not a mock!)"
    spySubscriber.receive("Hello subscribers") == "ok"
    spySubscriber.receive("Anyone there?") == "uh-oh"
  }

  def "Spies can modify behaviour and have interactions"() {
    given:
    def spyPublisher = Spy(Publisher) {
      send(_) >> { String message -> callRealMethodWithArgs("#" + message) }
    }
    def mockSubscriber = Mock(MySubscriber)
    spyPublisher.addSubscriber(mockSubscriber)

    when:
    spyPublisher.send("Hello subscribers")
    spyPublisher.send("Anyone there?")

    then: "check interactions"
    1 * mockSubscriber.receive("#Hello subscribers")
    1 * mockSubscriber.receive("#Anyone there?")
  }
}
Run Code Online (Sandbox Code Playgroud)


mik*_*ail 44

问题出在Spock框架的背景下,我不相信当前的答案会考虑到这一点.

基于Spock文档(示例定制,我自己的措辞添加):

存根: 用于使协作者以某种方式响应方法调用.在对方法进行存根化时,您不关心该方法的调用次数和次数; 你只是希望它在被调用时返回一些值,或者执行一些副作用.

subscriber.receive(_) >> "ok" // subscriber is a Stub()
Run Code Online (Sandbox Code Playgroud)

模拟: 用于描述规范下的对象与其协作者之间的交互.

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("hello") // subscriber is a Mock()
}
Run Code Online (Sandbox Code Playgroud)

模拟可以充当模拟和存根:

1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Run Code Online (Sandbox Code Playgroud)

间谍: 总是基于一个真实的对象与原始方法做真实的事情.可以像Stub一样使用来更改select方法的返回值.可以像模拟一样用来描述交互.

def subscriber = Spy(SubscriberImpl, constructorArgs: ["Fred"])

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") >> "ok" // subscriber is a Spy(), used as a Mock an Stub
}

def "should send message to subscriber (actually handle 'receive')"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("message1") // subscriber is a Spy(), used as a Mock, uses real 'receive' function
}
Run Code Online (Sandbox Code Playgroud)

摘要:

  • Stub()是一个Stub.
  • Mock()是Stub和Mock.
  • 间谍()是一个Stub,Mock和Spy.

如果Stub()足够,请避免使用Mock().

如果可以的话,避免使用Spy(),必须这样做可能是一种气味,暗示不正确的测试或不正确的测试对象设计.

  • “Spy() 是一个存根、模拟和间谍。” 这对sinon间谍来说不是真的吗? (2认同)
  • 我只是快速浏览了 Sinon 间谍,他们看起来不像 Mocks 或 Stubs。请注意,此问题/答案是在 Spock 的上下文中,即 Groovy,而不是 JS。 (2认同)

小智 12

简单来说:

模拟:你模拟了一个类型,在运行中你会得到一个对象.此模拟对象中的方法返回返回类型的默认值.

存根:您创建一个存根类,其中根据您的要求使用定义重新定义方法.例如:在实际对象方法中,您调用外部api并返回用户名和id.在存根对象方法中,您返回一些虚拟名称.

间谍:你创建一个真实的对象,然后你窥探它.现在你可以模拟一些方法,并选择不对某些方法这样做.

一个用法区别是你不能模拟方法级别对象.您可以在方法中创建默认对象,然后监视它以获得间谍对象中方法的所需行为.