testing - Spock 测试框架中 Mock/Stub/Spy 的区别

标签 testing mocking spock stub spy

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

最佳答案

注意:我将在接下来的段落中过度简化,甚至可能略微伪造。有关更多详细信息,请参阅 Martin Fowler's website .

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

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

spy 是真实对象和 stub 之间的混合体,即它基本上是真实对象,其中一些(不是全部)方法被 stub 方法遮蔽。非 stub 方法只是路由到原始对象。通过这种方式,您可以为“廉价”或简单的方法提供原始行为,为“昂贵”或复杂的方法提供虚假行为。


2017-02-06 更新:实际上用户 mikhail 的回答比我上面的原始回答更针对 Spock。所以在 Spock 的范围内,他所描述的是正确的,但这并不能证伪我的一般答案:

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

现在这是一个可执行的示例测试,展示了什么是可能的,什么不是。它比 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?")
  }
}

Groovy Web Console 中尝试.

关于testing - Spock 测试框架中 Mock/Stub/Spy 的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24413184/

相关文章:

docker - 使用 Flyway 和 Spring Boot 在 docker testcontainers 环境中迁移架构

linux - 执行bash脚本时如何显示行号

unit-testing - 如何用 PHPUnit 模拟这些方法?

.net - 我可以在这种情况下使用 Moq 吗?

mocking - 更改 Mock RPM 构建环境中的初始应用程序

java - Spock 测试与 junit 5 测试一起不运行

gradle - 如何在Gradle项目中同时运行JUnit5和Spock 2.0测试

testing - 如何在 Mocha/Chai 中使用 'OR'

unit-testing - 如何使用 JUnit 测试我的 servlet

testing - 强制 NetBeans 7.3 在测试运行时重新编译项目?