Perbedaan antara Mock / Stub / Spy dalam kerangka uji Spock


101

Saya tidak mengerti perbedaan antara Mock, Stub, dan Spy dalam pengujian Spock dan tutorial yang saya lihat online tidak menjelaskannya secara rinci.

Jawaban:


94

Perhatian: Saya akan menyederhanakan dan bahkan mungkin sedikit memalsukannya di paragraf yang akan datang. Untuk info lebih detail lihat situs web Martin Fowler .

Mock adalah kelas dummy menggantikan yang asli, mengembalikan sesuatu seperti null atau 0 untuk setiap panggilan metode. Anda menggunakan tiruan jika Anda memerlukan contoh dummy dari kelas kompleks yang jika tidak menggunakan sumber daya eksternal seperti koneksi jaringan, file atau database atau mungkin menggunakan lusinan objek lainnya. Keuntungan dari tiruan adalah Anda dapat mengisolasi kelas yang diuji dari seluruh sistem.

Stub juga merupakan kelas dummy yang memberikan beberapa hasil yang lebih spesifik, disiapkan atau direkam sebelumnya, dan diputar ulang untuk permintaan tertentu yang sedang diuji. Bisa dibilang stub adalah tiruan yang mewah. Di Spock Anda akan sering membaca tentang metode rintisan.

Mata-mata adalah sejenis hibrida antara objek nyata dan rintisan, yaitu pada dasarnya adalah objek nyata dengan beberapa (tidak semua) metode yang dibayangi oleh metode rintisan. Metode non-stub hanya diarahkan ke objek aslinya. Dengan cara ini Anda dapat memiliki perilaku asli untuk metode "murah" atau sepele dan perilaku palsu untuk metode "mahal" atau kompleks.


Pembaruan 2017-02-06: Sebenarnya jawaban mikhail pengguna lebih spesifik untuk Spock daripada yang asli saya di atas. Jadi dalam lingkup Spock, apa yang dia gambarkan benar, tapi itu tidak memalsukan jawaban umum saya:

  • Sebuah rintisan berkaitan dengan simulasi perilaku tertentu. Di Spock, ini semua yang bisa dilakukan rintisan, jadi ini adalah hal yang paling sederhana.
  • Tiruan berkaitan dengan berdiri di atas objek nyata (mungkin mahal), memberikan jawaban tanpa operasi untuk semua panggilan metode. Dalam hal ini, tiruan lebih sederhana daripada rintisan. Tapi di Spock, tiruan juga bisa hasil metode rintisan, yaitu menjadi tiruan dan rintisan. Selanjutnya, di Spock kita dapat menghitung seberapa sering metode tiruan tertentu dengan parameter tertentu dipanggil selama pengujian.
  • Mata-mata selalu membungkus objek nyata dan secara default mengarahkan semua panggilan metode ke objek asli, juga melewati hasil asli. Penghitungan panggilan metode juga berfungsi untuk mata-mata. Di Spock, mata-mata juga dapat memodifikasi perilaku objek asli, memanipulasi parameter panggilan metode dan / atau hasil atau memblokir metode asli agar tidak dipanggil sama sekali.

Sekarang di sini adalah contoh pengujian yang dapat dieksekusi, mendemonstrasikan apa yang mungkin dan apa yang tidak. Ini sedikit lebih instruktif daripada cuplikan mikhail. Terima kasih banyak kepadanya karena telah menginspirasi saya untuk meningkatkan jawaban saya sendiri! :-)

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

Perbedaan antara mock dan stub tidak jelas di sini. Dengan tiruan, seseorang ingin memverifikasi perilaku (jika dan berapa kali metode ini akan dipanggil). Dengan stub, seseorang hanya memverifikasi status (misalnya ukuran koleksi setelah pengujian). FYI: ejekan dapat memberikan hasil yang disiapkan juga.
chipiik

Terima kasih @mikhail dan chipiik atas tanggapan Anda. Saya telah memperbarui jawaban saya, semoga memperbaiki dan mengklarifikasi beberapa hal yang awalnya saya tulis. Penafian: Dalam jawaban asli saya, saya memang mengatakan bahwa saya terlalu menyederhanakan dan sedikit memalsukan beberapa fakta terkait Spock. Saya ingin orang-orang memahami perbedaan mendasar antara penyiksaan, ejekan, dan mata-mata.
kriegaex

@chipiik, satu hal lagi sebagai balasan atas komentar Anda: Saya telah melatih tim pengembangan selama bertahun-tahun dan melihat mereka menggunakan Spock atau JUnit lain dengan kerangka kerja tiruan lainnya. Dalam kebanyakan kasus saat menggunakan tiruan, mereka tidak melakukannya untuk memverifikasi perilaku (yaitu menghitung panggilan metode) tetapi untuk mengisolasi subjek yang diuji dari lingkungannya. IMO penghitungan interaksi hanyalah sebuah barang tambahan dan harus digunakan dengan hati-hati dan hemat karena ada kecenderungan untuk pengujian tersebut rusak ketika mereka menguji kabel komponen lebih dari perilaku sebenarnya.
kriegaex

Jawabannya yang singkat namun tetap sangat membantu
Chaklader Asfak Arefe

55

Pertanyaannya adalah dalam konteks kerangka kerja Spock dan saya tidak percaya jawaban saat ini memperhitungkan hal ini.

Berdasarkan dokumen Spock (contoh disesuaikan, kata-kata saya sendiri ditambahkan):

Stub: Digunakan untuk membuat kolaborator menanggapi panggilan metode dengan cara tertentu. Saat menghentikan suatu metode, Anda tidak peduli apakah dan berapa kali metode tersebut akan dipanggil; Anda hanya ingin mengembalikan nilai, atau melakukan beberapa efek samping, setiap kali dipanggil.

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

Mock: Digunakan untuk menggambarkan interaksi antara objek di bawah spesifikasi dan kolaboratornya.

def "should send message to subscriber"() {
    when:
        publisher.send("hello")

    then:
        1 * subscriber.receive("hello") // subscriber is a Mock()
}

A Mock dapat bertindak sebagai Mock dan Stub:

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

Spy: Selalu didasarkan pada objek nyata dengan metode orisinal yang melakukan hal-hal nyata. Dapat digunakan seperti Stub untuk mengubah nilai kembalian dari metode tertentu. Dapat digunakan seperti Mock untuk menggambarkan interaksi.

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
}

Ringkasan:

  • Stub () adalah Stub.
  • A Mock () adalah Stub dan Mock.
  • A Spy () adalah Stub, Mock and Spy.

Hindari menggunakan Mock () jika Stub () sudah cukup.

Hindari menggunakan Spy () jika Anda bisa, karena harus melakukannya bisa jadi bau dan petunjuk tentang pengujian yang salah atau desain yang salah dari objek yang diuji.


1
Sekadar menambahkan: Alasan lain Anda ingin meminimalkan penggunaan tiruan, adalah bahwa tiruan sangat mirip dengan pernyataan, karena Anda memeriksa hal-hal pada tiruan yang mungkin gagal dalam pengujian, dan Anda selalu ingin meminimalkan jumlah pemeriksaan yang Anda lakukan dalam ujian, agar tes tetap fokus dan sederhana. Jadi idealnya hanya ada satu tiruan per tes.
Sammi

1
"A Spy () adalah Stub, Mock and Spy." ini tidak benar untuk mata-mata sinon?
K - Toksisitas pada SO meningkat.

2
Saya baru saja melihat sekilas mata-mata Sinon dan mereka terlihat seperti tidak berperilaku sebagai Mocks atau Stubs. Perhatikan bahwa pertanyaan / jawaban ini ada dalam konteks Spock, yaitu Groovy, bukan JS.
mikhail

Ini harus menjadi jawaban yang benar, karena ini tercakup dalam konteks Spock. Juga, mengatakan bahwa stub adalah tiruan yang mewah mungkin menyesatkan, karena tiruan memiliki fungsi tambahan (memeriksa jumlah permintaan) yang tidak dimiliki rintisan (mengejek> lebih menarik daripada rintisan). Sekali lagi, mengejek dan bertopik sesuai Spock.
CGK

13

Secara sederhana:

Mock: Anda mengejek suatu tipe dan dengan cepat Anda mendapatkan sebuah objek yang dibuat. Metode dalam objek tiruan ini mengembalikan nilai default tipe pengembalian.

Stub: Anda membuat kelas rintisan di mana metode didefinisikan ulang dengan definisi sesuai kebutuhan Anda. Contoh: Dalam metode objek nyata Anda memanggil dan api eksternal dan mengembalikan nama pengguna dan id. Dalam metode objek stub Anda mengembalikan beberapa nama dummy.

Spy: Anda membuat satu objek nyata dan kemudian Anda memata-matai. Sekarang Anda dapat mengejek beberapa metode dan memilih untuk tidak melakukannya untuk beberapa metode.

Satu perbedaan penggunaan adalah Anda tidak dapat meniru objek tingkat metode. sedangkan Anda dapat membuat objek default dalam metode dan kemudian memata-matai untuk mendapatkan perilaku metode yang diinginkan dalam objek mata-mata.


0

Rintisan sebenarnya hanya untuk memfasilitasi pengujian unit, mereka bukan bagian dari pengujian. Ejekan, adalah bagian dari ujian, bagian dari verifikasi, bagian dari lulus / gagal.

Jadi, katakanlah Anda memiliki metode yang mengambil objek sebagai parameter. Anda tidak pernah melakukan apa pun yang mengubah parameter ini dalam pengujian. Anda cukup membaca nilai darinya. Itu rintisan.

Jika Anda mengubah sesuatu, atau perlu memverifikasi semacam interaksi dengan objek, maka itu adalah tiruan.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.