Różnica między Mock / Stub / Spy w środowisku testowym Spock


Odpowiedzi:


94

Uwaga: w kolejnych akapitach zamierzam nadmiernie uprościć, a może nawet trochę sfałszować. Więcej szczegółowych informacji można znaleźć na stronie Martina Fowlera .

Mock to fikcyjna klasa zastępująca prawdziwą, zwracająca coś w rodzaju null lub 0 dla każdego wywołania metody. Możesz użyć makiety, jeśli potrzebujesz fałszywej instancji złożonej klasy, która w innym przypadku korzystałaby z zasobów zewnętrznych, takich jak połączenia sieciowe, pliki lub bazy danych, lub może korzystałaby z dziesiątek innych obiektów. Zaletą mocków jest to, że można odizolować testowaną klasę od reszty systemu.

Odcinek jest również klasą fikcyjną zapewniającą bardziej szczegółowe, przygotowane lub wstępnie nagrane, odtworzone wyniki dla niektórych testowanych żądań. Można powiedzieć, że kikut to fantazyjna kpina. W Spocku często czytasz o metodach stubów.

Szpieg jest rodzajem hybrydy między rzeczywistym obiektem a kodem pośrednim, tj. Jest w zasadzie prawdziwym obiektem z niektórymi (nie wszystkimi) metodami przesłanymi za pomocą metod pośredniczących. Metody bez pośrednictwa są po prostu kierowane do oryginalnego obiektu. W ten sposób możesz mieć oryginalne zachowanie dla „tanich” lub trywialnych metod i fałszywe zachowanie dla „drogich” lub złożonych metod.


Aktualizacja 06.02.2017 : Właściwie odpowiedź użytkownika mikhail jest bardziej specyficzna dla Spocka niż moja oryginalna powyżej. Więc jeśli chodzi o Spocka, to, co opisuje, jest poprawne, ale to nie fałszuje mojej ogólnej odpowiedzi:

  • Kod stub zajmuje się symulowaniem określonego zachowania. W Spocku to wszystko, co może zrobić stub, więc jest to najprostsza rzecz.
  • Mock dotyczy zastępowania (prawdopodobnie kosztownego) rzeczywistego obiektu, zapewniającego odpowiedzi typu no-op dla wszystkich wywołań metod. Pod tym względem mock jest prostszy niż stub. Ale w Spocku, mock może również odgrywać wyniki metody, tj. Być zarówno mockiem, jak i stubem. Co więcej, w Spocku możemy policzyć, jak często podczas testu wywoływano konkretne metody pozorowane z określonymi parametrami.
  • Szpieg zawsze opakowuje rzeczywisty obiekt i domyślnie kieruje wszystkie wywołania metod do oryginalnego obiektu, również przechodząc przez oryginalne wyniki. Liczenie rozmów metod działa również w przypadku szpiegów. W Spock, szpieg może również modyfikować zachowanie oryginalnego obiektu, manipulując parametrami wywołania metody i / lub wynikami lub w ogóle blokując wywołanie oryginalnych metod.

Oto przykładowy test wykonywalny, pokazujący, co jest możliwe, a co nie. To trochę bardziej pouczające niż urywki Michaiła. Wielkie dzięki dla niego za zainspirowanie mnie do ulepszenia mojej własnej odpowiedzi! :-)

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

Różnica między mock i stub nie jest tutaj jasna. Przy mockach chce się zweryfikować zachowanie (czy i ile razy metoda będzie wywoływana). W przypadku kodów pośredniczących weryfikuje się tylko stan (np. Rozmiar kolekcji po teście). FYI: makiety mogą również dostarczyć przygotowanych wyników.
chipiik

Dzięki @mikhail i chipiik za twoją opinię. Zaktualizowałem moją odpowiedź, mam nadzieję, że poprawiłem i wyjaśniłem kilka rzeczy, które pierwotnie napisałem. Zastrzeżenie: W swojej oryginalnej odpowiedzi powiedziałem, że nadmiernie upraszczam i lekko fałszuję niektóre fakty związane ze Spockiem. Chciałem, aby ludzie zrozumieli podstawowe różnice między karczowaniem, kpieniem i szpiegowaniem.
kriegaex

@chipiik, jeszcze jedna rzecz w odpowiedzi na Twój komentarz: trenowałem zespoły programistyczne od wielu lat i widziałem, jak używają Spocka lub innego JUnita z innymi makietami. W większości przypadków, gdy używali mocków, nie robili tego w celu sprawdzenia zachowania (tj. Zliczania wywołań metod), ale w celu odizolowania badanego podmiotu od jego środowiska. Liczenie interakcji IMO jest tylko dodatkowym dodatkiem i powinno być używane rozważnie i oszczędnie, ponieważ takie testy mają tendencję do przerywania, gdy testują okablowanie komponentów bardziej niż ich rzeczywiste zachowanie.
kriegaex

Jego krótka, ale wciąż bardzo pomocna odpowiedź
Chaklader Asfak Arefe

55

Pytanie dotyczyło frameworka Spocka i nie sądzę, by obecne odpowiedzi to brały pod uwagę.

Na podstawie dokumentacji Spocka (dostosowane przykłady, moje własne sformułowanie dodane):

Stub: Służy do zmuszania współpracowników do odpowiadania na wywołania metod w określony sposób. Podczas kiczowania metody nie obchodzi Cię, czy i ile razy metoda będzie wywoływana; po prostu chcesz, aby zwracał jakąś wartość lub powodował jakiś efekt uboczny, gdy zostanie wywołany.

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

Mock: używany do opisywania interakcji między specyfikowanym obiektem a jego współpracownikami.

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

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

Mock może działać jako Mock i Stub:

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

Szpieg: zawsze opiera się na prawdziwym obiekcie z oryginalnymi metodami, które robią prawdziwe rzeczy. Może być używany jak Stub do zmiany wartości zwracanych metod wybranych. Może być używany jak makieta do opisywania interakcji.

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
}

Podsumowanie:

  • Stub () to Stub.
  • Mock () to skrót od Stub and Mock.
  • Spy () to Stub, Mock and Spy.

Unikaj używania Mock (), jeśli Stub () jest wystarczające.

Unikaj używania Spy (), jeśli możesz, ponieważ może to spowodować zapach i wskazówki dotyczące nieprawidłowego testu lub nieprawidłowego projektu testowanego obiektu.


1
Wystarczy dodać: Innym powodem, dla którego chcesz zminimalizować użycie makiet, jest to, że makieta jest bardzo podobna do asercji, ponieważ sprawdzasz rzeczy na makiecie, które mogą nie przejść testu, i zawsze chcesz zminimalizować liczbę sprawdzeń robisz w teście, aby był skoncentrowany i prosty. Idealnie więc powinno być tylko jeden test na test.
Sammi

1
„Szpieg () jest kodem, pozorem i szpiegiem”. to nie jest prawdą dla szpiegów grzechu?
K - Toksyczność SO rośnie.

2
Po prostu rzuciłem okiem na szpiegów Sinon i wyglądali, jakby nie zachowywali się jak Mocks lub Stubs. Zwróć uwagę, że to pytanie / odpowiedź jest w kontekście Spocka, którym jest Groovy, a nie JS.
mikhail

To powinna być poprawna odpowiedź, ponieważ dotyczy ona kontekstu Spocka. Ponadto stwierdzenie, że stub jest fantazyjnym mockiem, może być mylące, ponieważ mock ma dodatkową funkcjonalność (sprawdzanie liczby wywołań), której nie ma (mock> bardziej wyszukany niż stub). Znowu, mock and stubs jak na Spock.
CGK

13

W prostych słowach:

Mock: Kpisz sobie z typu iw locie otrzymujesz stworzony obiekt. Metody w tym obiekcie pozorowanym zwracają domyślne wartości typu zwracanego.

Stub: tworzysz klasę pośredniczącą, w której metody są przedefiniowane z definicją zgodnie z wymaganiami. Np. W rzeczywistej metodzie obiektowej wywołujesz zewnętrzny interfejs API i zwracasz nazwę użytkownika przeciw i id. W metodzie obiektu stubbed zwracasz fałszywą nazwę.

Szpieg: Tworzysz jeden prawdziwy obiekt, a następnie go szpiegujesz. Teraz możesz kpić z niektórych metod, a dla niektórych nie chcesz tego robić.

Jedną z różnic w użyciu jest to, że nie można mockować obiektów na poziomie metody. podczas gdy możesz utworzyć domyślny obiekt w metodzie, a następnie szpiegować go, aby uzyskać pożądane zachowanie metod w szpiegowanym obiekcie.


0

Stuby służą tylko do ułatwienia testu jednostkowego, nie są częścią testu. Mocks, są częścią testu, częścią weryfikacji, częścią zaliczenia / niezaliczenia.

Powiedzmy, że masz metodę, która przyjmuje obiekt jako parametr. Nigdy nie robisz niczego, co zmienia ten parametr w teście. Po prostu odczytujesz z niego wartość. To jest kikut.

Jeśli coś zmienisz lub musisz zweryfikować jakąś interakcję z obiektem, to jest to próba.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.