Nie rozumiem różnicy między testami Mock, Stub i Spy in Spock, a samouczki, które przeglądałem online, nie wyjaśniają ich szczegółowo.
Nie rozumiem różnicy między testami Mock, Stub i Spy in Spock, a samouczki, które przeglądałem online, nie wyjaśniają ich szczegółowo.
Odpowiedzi:
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:
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?")
}
}
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:
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.
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.
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.