回答:
注意:次の段落では、単純化しすぎて、おそらく少し改ざんするつもりです。詳細については、Martin FowlerのWebサイトを参照してください。
モックは、実際のクラスを置き換えるダミークラスであり、メソッド呼び出しごとにnullまたは0のようなものを返します。複雑なクラスのダミーインスタンスが必要な場合は、モックを使用します。そうしないと、ネットワーク接続、ファイル、データベースなどの外部リソースを使用したり、他のオブジェクトを何十も使用したりする可能性があります。モックの利点は、テスト中のクラスをシステムの他の部分から分離できることです。
スタブは、テスト中の特定のリクエストに対して、より具体的な、準備された、または事前に記録された、再生された結果を提供するダミークラスでもあります。スタブは派手なモックと言えます。Spockでは、スタブメソッドについてよく読みます。
スパイは、実際のオブジェクトとスタブのハイブリッドのようなものです。つまり、基本的には、スタブメソッドによって隠されたいくつかの(すべてではない)メソッドを持つ実際のオブジェクトです。スタブ化されていないメソッドは、元のオブジェクトにルーティングされるだけです。このようにして、「安価な」メソッドまたは自明なメソッドの元の動作と、「高価な」メソッドまたは複雑なメソッドの偽の動作を持たせることができます。
2017-02-06の更新:実際にユーザーmikhailの答えは、上記の私の元の答えよりもSpockに固有です。したがって、Spockの範囲内では、彼の説明は正しいですが、それは私の一般的な答えを偽ることはありません。
次に、実行可能なテストの例を示し、何が可能で何が不可能かを示します。ミハイルのスニペットよりも少し参考になります。私自身の答えを改善するように私を鼓舞してくれた彼に感謝します!:-)
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()
スパイ: 常に実際のオブジェクトに基づいており、実際のことを行う独自のメソッドを備えています。スタブのように使用して、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()の使用を避けます。
可能であればSpy()の使用を避けてください。そうする必要がある場合は、匂いが発生したり、不適切なテストやテスト対象のオブジェクトの設計が不適切だったりする可能性があります。
簡単な言葉で:
モック:タイプをモックし、その場でオブジェクトを作成します。このモックオブジェクトのメソッドは、戻り値の型のデフォルト値を返します。
スタブ:要件に応じた定義でメソッドが再定義されるスタブクラスを作成します。例:実際のオブジェクトメソッドで外部APIを呼び出し、IDに対してユーザー名を返します。スタブ化されたオブジェクトメソッドでは、ダミーの名前を返します。
スパイ:1つの実オブジェクトを作成してから、それをスパイします。これで、いくつかのメソッドをモックすることができ、一部のメソッドをモックしないことを選択しました。
使用法の違いの1つは、メソッドレベルのオブジェクトをモックできないことです。一方、メソッドでデフォルトのオブジェクトを作成し、それをスパイして、スパイされたオブジェクトでメソッドの望ましい動作を取得できます。