2014-06-25 6 views

Odpowiedz

57

Uwaga: zamierzam uprościć, a może nawet nieco sfałszować w kolejnych akapitach. Aby uzyskać szczegółowe informacje, patrz Martin Fowler's website.

Próba to fikcyjna klasa zastępująca prawdziwą, zwracająca coś podobnego do wartości zerowej lub 0 dla każdego wywołania metody. Używasz makiety, jeśli potrzebujesz pozornej instancji złożonej klasy, która w przeciwnym razie użyłaby zasobów zewnętrznych, takich jak połączenia sieciowe, pliki lub bazy danych, lub może użyć kilkudziesięciu innych obiektów. Zaletą mocks jest to, że można izolować testowaną klasę od reszty systemu.

Karczek jest również fikcyjną klasą zapewniającą bardziej szczegółowe, przygotowane lub wcześniej nagrane, powtórzone wyniki dla niektórych żądanych testów. Można powiedzieć, że stub jest fantazyjną sztuczką. W Spocku często czytasz o metodach skrótowych.

Szpieg jest rodzajem hybrydy między rzeczywistym obiektem a odgałęzieniem, tzn. Jest w zasadzie rzeczywistym obiektem z niektórymi (nie wszystkimi) metodami zasłoniętymi metodami stubowymi. Metody nieogniskowane są po prostu przekierowywane do oryginalnego obiektu. W ten sposób możesz mieć oryginalne zachowanie dla "tanich" lub trywialnych metod i fałszywego zachowania dla "drogich" lub złożonych metod.


Aktualizacja 06.02.2017: Faktycznie odpowiada Użytkownik Michaiła jest bardziej szczegółowy niż do Spocka moim pierwotnym powyżej. Więc w zakresie Spocka, to, co on opisuje jest poprawne, ale to nie falsyfikuje mojej ogólnej odpowiedzi:

  • Karczek zajmuje się symulowaniem określonego zachowania. W Spocku to wszystko może zrobić stub, więc jest to najprostsza rzecz.
  • Egzekucja dotyczy stania na (prawdopodobnie kosztownego) obiektu rzeczywistego, zapewniając odpowiedzi "no-op" dla wszystkich wywołań metod. Pod tym względem fikcja jest prostsza niż stub. Ale w Spocku próbka może również wywoływać wyniki metod, tj. Być zarówno makietą, jak i kikutem. Co więcej, w Spocku możemy policzyć, jak często określone metody próbne o określonych parametrach zostały wywołane podczas testu.
  • Szpieg zawsze opakowuje prawdziwy obiekt i domyślnie trasuje wszystkie wywołania metod do oryginalnego obiektu, również przechodząc przez oryginalne wyniki. Metoda liczenia połączeń działa również dla szpiegów. W Spocku szpieg może również modyfikować zachowanie oryginalnego obiektu, manipulując parametrami wywołania metody i/lub wynikami lub blokując oryginalne metody przed wywołaniem w ogóle.

Oto przykład wykonywanego testu, który pokazuje, co jest możliwe, a co nie. To jest trochę bardziej pouczające niż fragmenty mikhaila. Wielkie dzięki dla niego za inspirowanie mnie do poprawy 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?") 
    } 
} 
+0

Różnica między próbą i stubem nie jest tutaj jasna. W przypadku mocków należy zweryfikować zachowanie (jeśli i ile razy metoda zostanie wywołana). Za pomocą stubów jeden weryfikuje stan (np. Rozmiar kolekcji po teście). FYI: mock może dostarczyć również przygotowane wyniki. – chipiik

+0

Dzięki @mikhail i chipiik za Twoją opinię. Zaktualizowałem swoją odpowiedź, mając nadzieję, że poprawiam i wyjaśniam kilka rzeczy, które pierwotnie napisałem. Zastrzeżenie: W mojej oryginalnej odpowiedzi powiedziałem, że upraszczałem i nieco fałszowałem niektóre fakty związane ze Spockiem. Chciałem, aby ludzie zrozumieli podstawowe różnice między krępowaniem, kpiną i szpiegowaniem. – kriegaex

+0

@ chipiik, jeszcze jedno jako odpowiedź na twój komentarz: Przez wiele lat trenowałem zespoły programistów i widziałem, jak używają Spocka lub innego JUnita z innymi sztucznymi frameworkami. W większości przypadków, gdy używano makiet, nie robili tego w celu sprawdzenia zachowania (tj. Policzenia wywołań metod), ale wyizolowania badanego podmiotu z jego środowiska. Interoperacyjnie liczenie IMO jest tylko dodatkowym dodatkiem i powinno być używane starannie i oszczędnie, ponieważ istnieje tendencja do łamania takich testów, gdy testują okablowanie komponentów bardziej niż ich rzeczywiste zachowanie. – kriegaex

11

W prostych słowach:

Mock: Kpisz typ i na bieżąco można uzyskać obiekt utworzony. Metody w tym symulowanym obiekcie zwracają domyślne wartości zwracanego typu.

Stub: Tworzysz klasę pośrednią, w której metody są definiowane przedefiniowaniem zgodnie z wymaganiami. Przykład: w metodzie obiektu rzeczywistego wywołujesz i zewnętrzny interfejs API oraz zwracasz nazwę użytkownika i identyfikator. W metodzie obiektu skrótowego zwracamy nazwę dummy.

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

Jedną z różnic użytkowania jest Nie można kpić z obiektów poziomu metod. mając na uwadze, że można utworzyć obiekt domyślny w metodzie, a następnie szpiegować go, aby uzyskać pożądane zachowanie metod w obiekcie szpiegowskim.

25

Pytanie było w kontekście ram Spocka i nie sądzę, że obecne odpowiedzi biorą to pod uwagę.

podstawie Spock docs (przykłady niestandardowych, własne sformułowanie dodane):

skrótowe:wykorzystywane do wytwarzania współpracowników odpowiedzi na metoda nazywa się w określony sposób. Podczas stubowania metody nie obchodzi cię, czy i ile razy metoda zostanie wywołana; po prostu chcesz, aby zwracała jakąś wartość lub wykonała jakiś efekt uboczny, gdy zostanie wywołana.

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

Mock:używany do opisania interakcji między obiektem na podstawie opisu i jego współpracowników.

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

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

pozorowanej może działać jako Mock i zalążek:

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

Spy:zawsze opiera się na rzeczywistym obiekcie z oryginalnymi metodami, które robią prawdziwe rzeczy. Może być używany jak skrót, aby zmienić wartości zwracane wybranych metod. Może być używany jak Maniak 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:

  • szkieletem() jest nazwą.
  • Mock() to skrót i makiety.
  • Szpieg() to skrót, makieta i szpieg.

Unikaj używania makiety(), jeśli funkcja Stub() jest wystarczająca.

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

+1

Po prostu dodać: Kolejnym powodem, dla którego chcesz zminimalizować użycie mocks, jest to, że próbka jest bardzo podobna do aserta, ponieważ sprawdza się rzeczy na próbach, które mogą zawieść test, i zawsze chcesz zminimalizować liczbę kontroli przeprowadzanych w teście, aby test był prosty i skupiony. Najlepiej powinno być tylko jedno na próbę. – Sammi

+0

"Szpieg() to skrót, makieta i szpieg." to nie jest prawda dla szpiegów sinonowych? –

+0

Po prostu rzuciłem okiem na szpiegów Sinona i wyglądają, jakby nie zachowywali się jak Mocks czy Stubs. Zauważ, że to pytanie/odpowiedź są w kontekście Spocka, który jest Groovy, a nie JS. – mikhail

Powiązane problemy