有人可以解释一下Spock框架testing中的Mock,Stub和Spy之间的区别以及何时使用它们?

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

注意:我将在接下来的段落中简化甚至稍微伪造。 有关更多详细信息,请参阅Martin Fowler的网站 。

模拟是一个虚拟类replace一个真正的类,为每个方法调用返回类似null或0的东西。 如果您需要复杂类的虚拟实例,则可以使用模拟,否则将使用外部资源(如networking连接,文件或数据库),也可能使用许多其他对象。 嘲笑的好处是你可以隔离被testing的类和其他系统。

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

间谍是真实对象和存根之间的混合,也就是说,它基本上是存根方法所遮蔽的一些(不是全部)方法的真实对象。 非桩桩方法只是路由到原始的对象。 这样,对于“昂贵”或复杂的方法,您可以拥有“廉价”或微不足道的方法和假冒行为的原始行为。


更新2017-02-06:其实用户mikhail的答案是更特定于Spock比我上面的原始。 所以在Spock的范围内,他所描述的是正确的,但是这并不能certificate我的一般答案:

  • 存根是关于模拟特定行为的。 在Spock中,这是一个存根可以做的事情,所以它是最简单的事情。
  • 一个模拟是关于站在一个(可能是昂贵的)真实的对象,提供所有的方法调用无操作的答案。 在这方面,模拟比存根更简单。 但是在Spock中,模拟也可以存储方法结果,即既是模拟也是存根。 而且,在Spock中,我们可以计算在testing过程中具有特定参数的特定模拟方法的频率。
  • 间谍总是包装一个真实的对象,并默认将所有方法调用路由到原始对象,也通过原始结果。 方法调用计数也适用于间谍。 在Spock中,间谍还可以修改原始对象的行为,操作方法调用参数和/或结果,或阻止原始方法被调用。

现在这里是一个可执行的例子testing,展示什么是可能的,什么不是。 这比米哈伊尔的片段更具启发性。 非常感谢他激励我提高自己的答案! 🙂

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?") } } 

问题是在Spock框架的背景下,我不相信目前的答案考虑到这一点。

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

存根: 用于使协作者以某种方式响应方法调用。 在对一种方法进行存根化时,你并不关心该方法将被调用的次数和次数。 你只是想要它返回一些值,或者执行一些副作用,每当它被调用。

 subscriber.receive(_) >> "ok" // subscriber is a Stub() 

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

 def "should send message to subscriber"() { when: publisher.send("hello") then: 1 * subscriber.receive("hello") // subscriber is a Mock() } 

模拟可以作为模拟和存根:

 1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock() 

间谍: 总是基于一个真实的对象与原始的方法,做真实的事情。 可以像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 } 

概要:

  • 一个存根()是一个存根。
  • 模拟()是一个存根和模拟。
  • 间谍()是一个存根,模拟和间谍。

如果Stub()足够,则避免使用Mock()。

避免使用间谍(),如果可以的话,必须这样做可能是一种气味,并提示不正确的testing或不正确的devise的对象。

简单来说:

模拟:你嘲笑一个types,并在飞行中,你得到一个对象创build。 这个模拟对象中的方法返回返回types的默认值。

存根(stub):你创build一个存根类,根据你的需求定义方法。 例如:在实际对象方法中,您调用外部API,并返回用户名和ID。 在存根对象方法中,您返回一些虚拟名称。

间谍:你创造一个真实的物体,然后你监视它。 现在你可以嘲笑一些方法,并select不这样做的一些。

一个用法区别是你不能模拟方法级别的对象。 而你可以在方法中创build一个默认的对象,然后窥探它,以获得想要的方法在spied对象中的行为。