Spock test çerçevesinde Mock / Stub / Spy arasındaki fark


102

Spock testinde Mock, Stub ve Spy arasındaki farkı anlamıyorum ve çevrimiçi olarak baktığım eğitimler bunları ayrıntılı olarak açıklamıyor.

Yanıtlar:


96

Dikkat: Gelecek paragraflarda fazla basitleştireceğim ve hatta belki biraz tahrif edeceğim. Daha ayrıntılı bilgi için Martin Fowler'in web sitesine bakın .

Bir sahte, gerçek olanı değiştiren ve her yöntem çağrısı için boş veya 0 gibi bir değer döndüren sahte bir sınıftır. Aksi takdirde ağ bağlantıları, dosyalar veya veritabanları gibi harici kaynakları kullanacak veya belki düzinelerce başka nesne kullanacak karmaşık bir sınıfın sahte bir örneğine ihtiyacınız varsa bir taklit kullanırsınız. Taklitlerin avantajı, test edilen sınıfı sistemin geri kalanından ayırabilmenizdir.

Saplama aynı zamanda, test edilen belirli taleplere yönelik daha spesifik, hazırlanmış veya önceden kaydedilmiş, tekrar oynatılmış sonuçlar sağlayan sahte bir sınıftır. Saplama, süslü bir taklittir diyebilirsiniz. Spock'ta sık sık saplama yöntemlerini okuyacaksınız.

Casus, gerçek nesne ile saplama arasında bir tür melezdir, yani temelde saplama yöntemleriyle gölgelenen bazı (tümü değil) yöntemlerle gerçek nesnedir. Stubed olmayan yöntemler sadece orijinal nesneye yönlendirilir. Bu şekilde, "ucuz" veya önemsiz yöntemler için orijinal davranışa ve "pahalı" veya karmaşık yöntemler için sahte davranışa sahip olabilirsiniz.


Güncelleme 2017-02-06: Aslında kullanıcının mikhail'in cevabı, yukarıdaki orijinal cevaptan daha Spock'a özgü. Yani Spock kapsamında anlattıkları doğrudur, ancak bu benim genel cevabımı yanlışlamaz:

  • Saplama, belirli bir davranışı simüle etmekle ilgilidir. Spock'ta bu bir saplamanın yapabileceği her şeydir, bu yüzden en basit şey budur.
  • Bir taklit, (muhtemelen pahalı) bir gerçek nesne için ayakta durmakla ilgilidir ve tüm yöntem çağrıları için işlemsiz yanıtlar sağlar. Bu bağlamda, bir taklit saplama yapmaktan daha basittir. Ancak Spock'ta bir sahte, yöntem sonuçlarını da saplayabilir, yani hem sahte hem de saplama olabilir. Ayrıca, Spock'ta, bir test sırasında belirli parametrelere sahip belirli sahte yöntemlerin ne sıklıkla çağrıldığını sayabiliriz.
  • Bir casus her zaman gerçek bir nesneyi sarmalar ve varsayılan olarak tüm yöntem çağrılarını orijinal nesneye yönlendirir, ayrıca orijinal sonuçlardan da geçer. Yöntem çağrı sayma, casuslar için de işe yarar. Spock'ta bir casus ayrıca orijinal nesnenin davranışını değiştirebilir, yöntem çağrısı parametrelerini ve / veya sonuçlarını değiştirebilir veya orijinal yöntemlerin çağrılmasını tamamen engelleyebilir.

Şimdi burada neyin mümkün olup neyin olmadığını gösteren çalıştırılabilir bir örnek test var. Mikhail'in snippet'lerinden biraz daha öğretici. Kendi cevabımı geliştirmem için bana ilham verdiği için ona çok teşekkürler! :-)

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

Sahte ve saplama arasındaki fark burada net değil. Taklitlerle, kişi davranışı doğrulamak ister (yöntemin eğer ve kaç kez çağrılacağı). Saplamalarla, yalnızca durum doğrulanır (örneğin, testten sonra toplama boyutu). Bilginize: Taklitler de hazırlanmış sonuçlar sağlayabilir.
chipiik

Geri bildiriminiz için @mikhail ve chipiik'e teşekkürler. Cevabımı güncelledim, umarım başlangıçta yazdığım birkaç şeyi geliştirdim ve açıklığa kavuşturdum. Sorumluluk reddi: Orijinal cevabımda, Spock ile ilgili bazı gerçekleri aşırı basitleştirdiğimi ve biraz tahrif ettiğimi söyledim. İnsanların saplama, alay etme ve casusluk arasındaki temel farkları anlamalarını istedim.
kriegaex

@chipiik, yorumunuza yanıt olarak bir şey daha var: Uzun yıllardır geliştirme ekiplerine koçluk yapıyorum ve onların diğer sahte çerçevelerle Spock veya diğer JUnit'i kullandıklarını gördüm. Çoğu durumda, taklitler kullanılırken, bunu davranışı doğrulamak (yani yöntem çağrılarını saymak) için değil, test edilen özneyi ortamından izole etmek için yaptılar. Etkileşim sayma IMO sadece bir eklentidir ve dikkatlice ve idareli kullanılmalıdır çünkü bu tür testlerin, bileşenlerin kablolarını gerçek davranışlarından daha fazla test ettiklerinde kırılma eğilimi vardır.
kriegaex

Kısa ama yine de çok faydalı cevap
Chaklader Asfak Arefe

55

Soru Spock çerçevesi bağlamındaydı ve şu anki yanıtların bunu hesaba kattığına inanmıyorum.

Dayanarak Spock docs (örnekler, kendi ifadeler eklendi özelleştirilmiş):

Stub: Ortak çalışanların yöntem çağrılarına belirli bir şekilde yanıt vermesini sağlamak için kullanılır. Bir yöntemi saplarken, yöntemin kaç kez çağrılacağı umurunuzda değildir; her çağrıldığında bir değer döndürmesini veya bazı yan etkiler gerçekleştirmesini istersiniz.

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

Mock: Spesifikasyon altındaki nesne ile işbirlikçileri arasındaki etkileşimleri tanımlamak için kullanılır.

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

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

Mock, Mock ve Stub olarak hareket edebilir:

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

Casus: Her zaman gerçek şeyler yapan orijinal yöntemlerle gerçek bir nesneye dayanır. Seçilen yöntemlerin dönüş değerlerini değiştirmek için bir Stub gibi kullanılabilir. Etkileşimleri açıklamak için bir Mock gibi kullanılabilir.

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
}

Özet:

  • Bir Saplama () bir Saplamadır.
  • Bir Mock (), bir Saplama ve Sahte'dir.
  • Casus () bir Saplama, Sahte ve Casustur.

Stub () yeterliyse Mock () kullanmaktan kaçının.

Yapabiliyorsanız, Spy () kullanmaktan kaçının, bunu yapmak zorunda kalmak bir koku olabilir ve yanlış test veya test edilen nesnenin yanlış tasarımı olabilir.


1
Eklemek gerekirse: Taklit kullanımını en aza indirmek istemenizin bir başka nedeni de, taklitin bir iddiaya çok benzemesi, testte başarısız olabilecek şeyleri bir sahte üzerinde kontrol etmeniz ve her zaman kontrol miktarını en aza indirmek istemenizdir. testi odaklanmış ve basit tutmak için bir test yaparsınız. Bu nedenle ideal olarak, test başına yalnızca bir model olmalıdır.
Sammi

1
"Casus () bir Saplama, Sahte ve Casustur." bu sinon casusları için doğru değil mi?
basickarl

2
Sinon casuslarına hızlıca baktım ve Mocks veya Stubs gibi davranmıyorlarmış gibi görünüyorlar. Bu sorunun / cevabın JS değil, Groovy olan Spock bağlamında olduğuna dikkat edin.
mikhail

Kapsamı Spock bağlamında olduğundan bu doğru yanıt olmalıdır. Ayrıca, bir saplamanın süslü bir sahte olduğunu söylemek yanıltıcı olabilir, çünkü bir sahte, saplamanın sahip olmadığı (sahte> saplamadan daha meraklı) ekstra işlevselliğe (çağrı sayısını kontrol etme) sahiptir. Yine, Spock'a göre alay ve taslaklar.
CGK

13

Basit bir ifadeyle:

Mock: Bir yazı ile alay edersiniz ve anında yaratılan bir nesne elde edersiniz. Bu sahte nesnedeki yöntemler, dönüş türünün varsayılan değerlerini döndürür.

Stub: Yöntemlerin gereksiniminize göre tanımla yeniden tanımlandığı bir saplama sınıfı oluşturursunuz. Ör: Gerçek nesne yönteminde, harici api'yi çağırırsınız ve kullanıcı adını ve kimliğine karşı döndürürsünüz. Stubbed nesne yönteminde bazı sahte adlar döndürürsünüz.

Casus: Bir gerçek nesne yaratırsınız ve sonra onu gözetliyorsunuz. Şimdi bazı yöntemlerle dalga geçebilir ve bazıları için bunu yapmamayı seçebilirsiniz.

Bir kullanım farkı, yöntem düzeyindeki nesnelerle dalga geçememenizdir. oysa yöntemde varsayılan bir nesne oluşturabilir ve ardından casus nesnede yöntemlerin istenen davranışını elde etmek için onu gözetleyebilirsiniz.


0

Stub'lar gerçekten sadece birim testini kolaylaştırmak içindir, testin bir parçası değildirler. Taklitler, testin, doğrulamanın, geçti / kalmanın bir parçasıdır.

Diyelim ki bir nesneyi parametre olarak alan bir yönteminiz var. Testte bu parametreyi değiştiren hiçbir şeyi asla yapmazsınız. Ondan bir değer okursunuz. Bu bir taslak.

Herhangi bir şeyi değiştirirseniz veya nesneyle bir tür etkileşimi doğrulamanız gerekirse, bu bir sahtedir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.