ฉันไม่เข้าใจความแตกต่างระหว่างการทดสอบ Mock, Stub และ Spy ใน Spock และแบบฝึกหัดที่ฉันดูทางออนไลน์ไม่ได้อธิบายรายละเอียดไว้
ฉันไม่เข้าใจความแตกต่างระหว่างการทดสอบ Mock, Stub และ Spy ใน Spock และแบบฝึกหัดที่ฉันดูทางออนไลน์ไม่ได้อธิบายรายละเอียดไว้
คำตอบ:
ข้อควรระวัง: ฉันจะทำให้เข้าใจผิดมากเกินไปและอาจจะผิดพลาดเล็กน้อยในย่อหน้าที่กำลังจะมาถึง สำหรับข้อมูลเพิ่มเติมโปรดดูรายละเอียดเว็บไซต์ของมาร์ตินฟาวเลอร์
การจำลองคือคลาสดัมมี่ที่แทนที่ของจริงโดยส่งคืนค่าบางอย่างเช่น 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 (ตัวอย่างที่กำหนดเองเพิ่มถ้อยคำของฉันเอง):
Stub: ใช้เพื่อให้ผู้ทำงานร่วมกันตอบสนองต่อวิธีการโทรในลักษณะหนึ่ง เมื่อขีดฆ่าเมธอดคุณจะไม่สนใจว่าเมธอดนั้นจะถูกเรียกใช้กี่ครั้งและกี่ครั้ง คุณแค่ต้องการให้มันคืนค่าบางอย่างหรือแสดงผลข้างเคียงบางอย่างเมื่อใดก็ตามที่ถูกเรียก
subscriber.receive(_) >> "ok" // subscriber is a Stub()
Mock: ใช้เพื่ออธิบายปฏิสัมพันธ์ระหว่างวัตถุภายใต้ข้อกำหนดและผู้ทำงานร่วมกัน
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: มักจะขึ้นอยู่กับวัตถุจริงด้วยวิธีการดั้งเดิมที่ทำของจริง สามารถใช้เหมือน Stub เพื่อเปลี่ยนค่าการส่งคืนของวิธีการเลือก สามารถใช้เหมือน 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
}
สรุป:
หลีกเลี่ยงการใช้ Mock () หาก Stub () เพียงพอ
หลีกเลี่ยงการใช้ Spy () หากทำได้การทำเช่นนั้นอาจเป็นกลิ่นและคำแนะนำในการทดสอบที่ไม่ถูกต้องหรือการออกแบบวัตถุที่ไม่ถูกต้องภายใต้การทดสอบ
พูดง่ายๆคือ
จำลอง: คุณล้อเลียนประเภทและทันทีที่คุณได้รับวัตถุที่สร้างขึ้น วิธีการในวัตถุจำลองนี้จะส่งคืนค่าเริ่มต้นของประเภทการส่งคืน
Stub: คุณสร้างคลาส Stub ที่มีการกำหนดวิธีการใหม่ด้วยคำจำกัดความตามความต้องการของคุณ เช่นในวิธีออบเจ็กต์จริงคุณเรียกใช้และ API ภายนอกและส่งคืนชื่อผู้ใช้และ id ในเมธอดวัตถุที่ถูกดึงคุณจะส่งคืนชื่อจำลอง
Spy: คุณสร้างวัตถุจริงขึ้นมาหนึ่งชิ้นแล้วสอดแนม ตอนนี้คุณสามารถล้อเลียนวิธีการบางอย่างและเลือกที่จะไม่ทำสำหรับบางวิธี
ความแตกต่างในการใช้งานอย่างหนึ่งคือคุณไม่สามารถจำลองวัตถุระดับวิธีการได้ ในขณะที่คุณสามารถสร้างวัตถุเริ่มต้นในวิธีการแล้วสอดแนมเพื่อให้ได้พฤติกรรมที่ต้องการของวิธีการในวัตถุสอดแนม
Stubs เป็นเพียงเพื่ออำนวยความสะดวกในการทดสอบหน่วยเท่านั้น แต่ไม่ได้เป็นส่วนหนึ่งของการทดสอบ Mocks เป็นส่วนหนึ่งของการทดสอบส่วนหนึ่งของการตรวจสอบส่วนหนึ่งของการผ่าน / ไม่ผ่าน
สมมติว่าคุณมีวิธีการที่ใช้วัตถุเป็นพารามิเตอร์ คุณไม่เคยทำอะไรที่เปลี่ยนแปลงพารามิเตอร์นี้ในการทดสอบ คุณเพียงแค่อ่านค่าจากมัน นั่นคือต้นขั้ว
หากคุณเปลี่ยนแปลงสิ่งใดหรือต้องการตรวจสอบการโต้ตอบบางอย่างกับวัตถุแสดงว่าเป็นการเยาะเย้ย