我不理解Spock测试中Mock、Stub和Spy之间的区别,我在网上查看的教程并没有详细解释它们。
注意:在下面的段落中,我将会过度简化,甚至可能有些虚假。如果需要更详细的信息,请参考Martin Fowler的网站。
Mock是一个代替真实类的虚拟类,在每个方法调用时返回诸如null或0之类的东西。如果您需要复杂类的虚拟实例,否则此类将使用外部资源(如网络连接、文件或数据库),或者可能使用其他几十个对象,则可以使用Mock。 Mock的优点是可以将被测试的类与系统的其余部分隔离。
Stub也是一个虚拟类,为某些受测试请求提供一些更具体、已准备或预录制的结果。你可以说Stub是一个华丽的Mock。在Spock中,您经常会看到关于Stub方法的内容。
Spy是一种真实对象和Stub之间的混合体,即它基本上是具有某些(不是全部)方法由Stub方法阴影化的真实对象。未阴影化的方法只是通过原始对象路由。这样,您可以获得"便宜"或微不足道的方法的原始行为和"昂贵"或复杂方法的伪行为。
更新2017-02-06:实际上,用户mikhail的答案更具体,适用于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?")
}
}
在Groovy Web Console中尝试一下。
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()
}
一个 Mock 可以兼具 Mock 和 Stub 的功能:
1 * subscriber.receive("message1") >> "ok" // subscriber is a Mock()
Spy: 总是基于一个真实的对象,具有原始方法来完成真正的事情。可以像存根一样使用,改变选择方法的返回值。也可以像模拟一样描述交互。
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()。
如果可以的话,请避免使用Spy(),必须使用它可能会有问题,并暗示测试不正确或被测试对象设计不正确。
简单来说:
Mock: 你模拟一个类型并即时创建一个对象。该模拟对象中的方法返回默认的返回值。
Stub: 你创建一个存根类,在其中重新定义方法以符合你的要求。例如:在真实对象的方法中,你调用外部API并返回ID对应的用户名。在存根对象的方法中,你返回一些虚拟名称。
Spy: 你创建一个真实的对象,然后进行监视。现在,你可以模拟某些方法并选择不这样做某些方法。
一个使用上的区别是你无法模拟方法级别的对象。但你可以在方法中创建一个默认对象,然后对其进行监视,以获取所监视对象中方法的期望行为。
存根(Stubs)只是为了方便单元测试而存在的,它们并不是测试的一部分。模拟对象(Mocks)则是测试的一部分,用于验证、用于通过或失败的一部分。
比如说你有一个方法,它以对象作为参数。在测试中,你从未改变过这个参数,只是从中读取某个值。这就是一个存根。
如果你改变了任何东西或需要验证与该对象的某种交互作用,那么它就是模拟对象。