Answers:
注意:在接下来的几段中,我将过分简化,甚至稍加捏造。有关更多详细信息,请参阅Martin Fowler的网站。
模拟是一个虚拟类,它替换了一个真实的类,为每个方法调用返回类似null或0的值。如果需要复杂类的虚拟实例,则可以使用模拟,否则将使用外部资源,例如网络连接,文件或数据库,或者可能使用许多其他对象。模拟的优点是您可以将测试中的类与系统的其余部分隔离。
存根也是一个虚拟类,为某些被测请求提供了一些更具体的,准备好的或预先记录的,重放的结果。您可以说存根是一种幻想。在Spock中,您将经常阅读有关存根方法的信息。
间谍是真实对象和存根之间的混合体,即,它基本上是带有存根方法所遮盖的某些(不是全部)方法的真实对象。非存根方法只是被路由到原始对象。这样,对于“便宜”或琐碎的方法,您可以具有原始行为,对于“昂贵”或复杂的方法,则可以具有假行为。
更新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?")
}
}
问题是在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()
间谍: 始终基于具有执行真实操作的原始方法的真实对象。可以像存根一样用于更改选择方法的返回值。可以像模拟一样用来描述交互。
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(),否则可能会闻到气味并提示测试不正确或被测试对象的设计不正确。