Jaki jest cel pozorowanych obiektów?

167

Jestem nowy w testowaniu jednostkowym i ciągle słyszę słowa „pozorowane obiekty” rzucane wokół. Mówiąc prostym językiem, czy ktoś może wyjaśnić, czym są pozorowane obiekty i do czego są zwykle używane podczas pisania testów jednostkowych?

agentbanks217
źródło
12
Są narzędziem do masowego przepracowywania rzeczy z elastycznością, której nie potrzebujesz do rozwiązania problemu.
dsimcha
2
możliwy duplikat What is Mocking?
nawfal

Odpowiedzi:

360

Ponieważ mówisz, że jesteś nowy w testowaniu jednostkowym i prosiłeś o pozorowane obiekty w „terminach laika”, spróbuję użyć przykładu laika.

Testów jednostkowych

Wyobraź sobie testy jednostkowe dla tego systemu:

cook <- waiter <- customer

Ogólnie łatwo jest wyobrazić sobie testowanie komponentu niskiego poziomu, takiego jak cook:

cook <- test driver

Kierowca testowy po prostu zamawia różne dania i sprawdza, czy kucharz zwraca właściwe danie dla każdego zamówienia.

Trudniej jest przetestować środkowy komponent, taki jak kelner, który wykorzystuje zachowanie innych komponentów. Naiwny tester mógłby przetestować komponent kelnera w ten sam sposób, w jaki testowaliśmy komponent cook:

cook <- waiter <- test driver

Kierowca testowy zamawiał różne dania i upewniał się, że kelner zwraca właściwe danie. Niestety oznacza to, że ten test komponentu kelnerskiego może zależeć od prawidłowego zachowania się komponentu gotującego. Zależność ta jest jeszcze gorsza, jeśli składnik gotowania ma jakiekolwiek cechy nieprzyjazne dla testów, takie jak zachowanie niedeterministyczne (menu zawiera niespodziankę szefa kuchni jako danie), wiele zależności (kucharz nie będzie gotować bez całego personelu) lub wiele zasoby (niektóre dania wymagają drogich składników lub gotowanie trwa godzinę).

Ponieważ jest to test kelnerski, najlepiej byłoby przetestować tylko kelnera, a nie kucharza. W szczególności chcemy mieć pewność, że kelner prawidłowo przekazuje zamówienie klienta kucharzowi i prawidłowo dostarcza potrawy kucharza do klienta.

Testowanie jednostkowe oznacza niezależne testowanie jednostek, więc lepszym podejściem byłoby wyodrębnienie testowanego komponentu (kelnera) za pomocą tego, co Fowler nazywa testowymi dublami (manekiny, stuby, podróbki, mocky) .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Tutaj kucharz testowy jest „w zmowie” z kierowcą testowym. Idealnie, testowany system jest zaprojektowany tak, aby kucharz testowy mógł być łatwo zastąpiony ( wstrzyknięty ) do pracy z kelnerem bez zmiany kodu produkcji (np. Bez zmiany kodu kelnera).

Mock Objects

Teraz gotowanie testowe (test podwójny) można zaimplementować na różne sposoby:

  • fałszywy kucharz - ktoś udający kucharza używając mrożonek i kuchenki mikrofalowej,
  • stub cook - sprzedawca hot dogów, który zawsze daje ci hot dogi bez względu na to, co zamówisz, lub
  • udawany kucharz - tajny policjant wykonujący scenariusz, udający kucharza podczas operacji żądła.

Zobacz artykuł Fowlera, aby uzyskać więcej informacji na temat podróbek vs niedopałków vs mocków vs manekinów , ale na razie skupmy się na pozorowanym kucharzu.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

Duża część testowania jednostkowego składnika kelnera koncentruje się na tym, jak kelner współdziała z komponentem Cook. Podejście oparte na próbach skupia się na pełnym określeniu, czym jest właściwa interakcja i wykrywaniu, kiedy jest nie tak.

Obiekt pozorowany z góry wie, co ma się wydarzyć podczas testu (np. Która z jego metod wywoła wywołanie itp.), A obiekt pozorowany wie, jak ma zareagować (np. Jaką wartość zwracaną ma dostarczyć). Makieta wskaże, czy to, co naprawdę się dzieje, różni się od tego, co powinno się wydarzyć. Dla każdego przypadku testowego można by stworzyć od podstaw niestandardowy obiekt pozorowany, aby wykonać oczekiwane zachowanie dla tego przypadku testowego, ale frameworka do pozorowania stara się umożliwić jasne i łatwe wskazanie takiej specyfikacji zachowania bezpośrednio w przypadku testowym.

Rozmowa wokół testu opartego na próbach może wyglądać następująco:

kierowca testowy, aby udawać kucharza : spodziewaj się zamówienia na hot doga iw odpowiedzi daj mu tego atrapę hot doga

kierowca testowy (udający klienta) do kelnera : poproszę hot doga proszę
kelnera, aby udawał kucharza : 1 hot doga proszę
udawać kucharza do kelnera : zamów: 1 hot dog gotowy (daje kelnerowi atrapę hot doga)
kelner do testowego kierowcy : oto twój hot dog (daje fałszywego hot doga kierowcy testowego)

sterownik testowy : TEST POWODZI SIĘ!

Ale ponieważ nasz kelner jest nowy, może się tak stać:

kierowca testowy, aby udawać kucharza : spodziewaj się zamówienia na hot doga iw odpowiedzi daj mu tego atrapę hot doga

kierowca testowy (udający klienta) do kelnera : Poproszę hot doga, proszę
kelnera, aby udawał kucharza : 1 hamburger, proszę,
kucharz pozorowany zatrzymuje test: powiedziano mi, że mam się spodziewać zamówienia na hot doga!

kierowca testowy zauważa problem: TEST FAILED! - kelner zmienił zamówienie

lub

kierowca testowy, aby udawać kucharza : spodziewaj się zamówienia na hot doga iw odpowiedzi daj mu tego atrapę hot doga

kierowca testowy (udający klienta) do kelnera : poproszę hot doga proszę
kelnera, aby udawał kucharza : 1 hot doga proszę
udawać kucharza do kelnera : zamów: 1 hot dog gotowy (daje kelnerowi atrapę hot doga)
kelner do testowego kierowcy : oto twoje frytki (podaje frytki z innego zamówienia w celu przetestowania kierowcy)

kierowca testowy zauważa nieoczekiwane frytki: TEST FAILED! kelner zwrócił złe danie

Może być trudno dostrzec różnicę między pozorowanymi obiektami i kodami pośredniczącymi bez kontrastującego przykładu opartego na stubach, ale ta odpowiedź jest już zbyt długa :-)

Należy również zauważyć, że jest to dość uproszczony przykład i że frameworki do pozorowania pozwalają na dość wyrafinowane specyfikacje oczekiwanego zachowania komponentów w celu obsługi kompleksowych testów. Więcej informacji można znaleźć w materiałach na temat pozorowanych obiektów i fałszywych struktur.

Bert F.
źródło
12
To świetne wyjaśnienie, ale czy nie testujesz do pewnego stopnia implementacji kelnera? W twoim przypadku prawdopodobnie jest w porządku, ponieważ sprawdzasz, czy używa on prawidłowego interfejsu API, ale co, jeśli są na to różne sposoby, a kelner może wybrać jeden lub drugi? Pomyślałem, że celem testów jednostkowych było przetestowanie API, a nie implementacja. (To jest pytanie, które zawsze zadaję sobie, gdy czytam o
kpinie
8
Dzięki. Nie mogę powiedzieć, czy testujemy „implementację”, nie widząc (lub nie definiując) specyfikacji kelnera. Można założyć, że kelnerowi wolno samemu ugotować danie lub zrealizować zamówienie na ulicy, ale zakładam, że specyfikacja kelnera obejmuje korzystanie z zamierzonego szefa kuchni - w końcu szef produkcji jest drogim, wyśmienitym szefem kuchni, a my ” wolałbym, żeby nasz kelner go używał. Myślę, że bez tej specyfikacji musiałbym stwierdzić, że masz rację - kelner może zrealizować zamówienie, jak chce, aby było „poprawne”. OTOH, bez specyfikacji, test jest bez znaczenia. [Kontynuacja ...]
Bert F
8
NIGDY nie robisz wielkiego spostrzeżenia, które prowadzi do fantastycznego tematu testów jednostkowych typu white box vs black box. Nie sądzę, aby istniał konsensus branżowy, który mówi, że testowanie jednostkowe musi być czarną skrzynką zamiast białej skrzynki („testuj API, a nie implementację”). Myślę, że najlepszym testowaniem jednostkowym powinno być prawdopodobnie połączenie tych dwóch, aby zrównoważyć kruchość testów z pokryciem kodu i kompletnością przypadków testowych.
Bert F
1
Według mnie ta odpowiedź nie jest wystarczająco techniczna. Chcę wiedzieć, dlaczego powinienem używać pozorowanego obiektu, kiedy mogę używać prawdziwych obiektów.
Niklas R.
1
Świetne wyjaśnienie !! Dziękuję Ci!! @BertF
Murali
28

Mock Object to obiekt, który zastępuje rzeczywisty obiekt. W programowaniu obiektowym pozorowane obiekty to symulowane obiekty, które naśladują zachowanie rzeczywistych obiektów w kontrolowany sposób.

Programista komputerowy zazwyczaj tworzy pozorowany obiekt, aby przetestować zachowanie innego obiektu, w podobny sposób, w jaki projektant samochodu używa manekina do testów zderzeniowych do symulacji dynamicznego zachowania człowieka w zderzeniu pojazdu.

http://en.wikipedia.org/wiki/Mock_object

Obiekty pozorowane umożliwiają konfigurowanie scenariuszy testowych bez obciążania dużych, nieporęcznych zasobów, takich jak bazy danych. Zamiast wywoływać bazę danych do testowania, możesz symulować swoją bazę danych przy użyciu pozorowanego obiektu w testach jednostkowych. To uwalnia Cię od konieczności konfigurowania i niszczenia prawdziwej bazy danych, aby przetestować tylko jedną metodę w swojej klasie.

Słowo „Mock” ​​jest czasami błędnie używane zamiennie z „Stub”. Tutaj opisano różnice między tymi dwoma słowami . Zasadniczo, makieta to obiekt pośredniczący, który zawiera również oczekiwania (tj. „Potwierdzenia”) dotyczące właściwego zachowania testowanego obiektu / metody.

Na przykład:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Zauważ, że obiekty warehousei mailermakiety są zaprogramowane z oczekiwanymi wynikami.

Robert Harvey
źródło
2
Definicja, którą podałeś, różni się w najmniejszym stopniu od „obiektu pośredniczącego” i jako taka nie wyjaśnia, czym jest pozorowany obiekt.
Brent Arias
Inna poprawka: „słowo„ Mock ”jest czasami błędnie używane zamiennie z„ stub ””.
Brent Arias
@Myst: Użycie tych dwóch słów nie jest uniwersalne; różni się w zależności od autorów. Fowler tak mówi i artykuł w Wikipedii tak mówi. Możesz jednak edytować zmianę i usunąć swój głos przeciw. :)
Robert Harvey
1
Zgadzam się z Robertem: użycie słowa „makieta” zwykle różni się w całej branży, ale nie ma określonej definicji zgodnie z moim doświadczeniem, z wyjątkiem tego, że zazwyczaj NIE jest to faktycznie testowany obiekt, a raczej istnieje, aby ułatwić testowanie, gdy używa się rzeczywistego przedmiot lub wszystkie jego części byłyby bardzo niewygodne i nie miałyby większego znaczenia.
mkelley33
15

Mockowe obiekty to symulowane obiekty, które naśladują zachowanie prawdziwych. Zwykle piszesz obiekt pozorowany, jeśli:

  • Rzeczywisty obiekt jest zbyt złożony, aby można go było włączyć do testów jednostkowych (na przykład w komunikacji sieciowej można mieć pozorowany obiekt, który będzie symulował bycie drugim partnerem)
  • Wynik twojego obiektu nie jest deterministyczny
  • Prawdziwy obiekt nie jest jeszcze dostępny
Dani Cricco
źródło
12

Obiekt Mock jest jednym z rodzajów Double Test . Używasz mockobjects do testowania i weryfikowania protokołu / interakcji testowanej klasy z innymi klasami.

Zwykle będziesz w pewnym sensie „program” lub „rejestrować” oczekiwania: wywołania metod, których oczekujesz od klasy względem obiektu bazowego.

Załóżmy na przykład, że testujemy metodę usługi, aby zaktualizować pole w widżecie. I że w twojej architekturze jest WidgetDAO, który zajmuje się bazą danych. Rozmowa z bazą danych jest powolna, a jej konfigurowanie i późniejsze czyszczenie jest skomplikowane, dlatego będziemy kpić z Widgetu

zastanówmy się, co usługa musi zrobić: powinna pobrać Widget z bazy danych, coś z tym zrobić i ponownie zapisać.

Czyli w pseudojęzyku z pseudo-próbną biblioteką mielibyśmy coś takiego:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

W ten sposób możemy łatwo przetestować rozwój klas zależnych od innych klas.

Peter Tillemans
źródło
11

Gorąco polecam świetny artykuł Martina Fowlera wyjaśniający, czym dokładnie są mocks i czym różnią się od stubów.

Adam Byrtek
źródło
10
Niezupełnie przyjazny dla początkujących, prawda?
Robert Harvey
@Robert Harvey: Może, w każdym razie dobrze widzieć, że pomogło mi to w wyjaśnieniu odpowiedzi :)
Adam Byrtek
Artykuły Martina Fowlera są napisane na sposób specyfikacji RFC: suche i zimne.
revo
9

Podczas testowania jednostkowego jakiejś części programu komputerowego najlepiej byłoby przetestować tylko zachowanie tej określonej części.

Na przykład spójrz na poniższy pseudokod z wyimaginowanego fragmentu programu, który używa innego programu do wywołania funkcji print:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Gdybyś to testował, chciałbyś głównie przetestować część, która sprawdza, czy użytkownik jest Fredem, czy nie. Naprawdę nie chcesz testować Printerczęści rzeczy. To byłby kolejny test.

W tym miejscu pojawiają się pozorowane obiekty. Udają inne rodzaje rzeczy. W takim przypadku użyłbyś Mocka, Printeraby działał jak prawdziwa drukarka, ale nie robiłby niewygodnych rzeczy, takich jak drukowanie.


Istnieje kilka innych typów udawanych obiektów, które nie są makietami. Główną rzeczą, która sprawia, że ​​Mocks Mocks jest to, że można je skonfigurować za pomocą zachowań i oczekiwań.

Oczekiwania pozwalają Twojemu Mockowi wywołać błąd, gdy jest używany nieprawidłowo. W powyższym przykładzie możesz chcieć mieć pewność, że Printer jest wywoływana z HelloFred w przypadku testowym „user is Fred”. Jeśli tak się nie stanie, Twój Mock może Cię ostrzec.

Zachowanie w Mocks oznacza, że ​​na przykład Twój kod zrobił coś takiego:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Teraz chcesz przetestować, co robi twój kod, gdy wywoływana jest Printer i zwraca SaidHello, więc możesz skonfigurować Mocka, aby zwracał SaidHello, gdy jest wywoływany z HelloFred.

Jednym z dobrych źródeł na ten temat jest post Martina Fowlersa Mocks Aren't Stubs

David Hall
źródło
7

Obiekty makiety i obiekty pośredniczące są kluczową częścią testów jednostkowych. W rzeczywistości przechodzą długą drogę, aby upewnić się, że testujesz jednostki , a nie grupy jednostek.

Krótko mówiąc, używasz stubów, aby zerwać zależność SUT (System Under Test) od innych obiektów i makiet, aby to zrobić, i sprawdzić, czy SUT wywołał pewne metody / właściwości w zależności. Wracamy do fundamentalnych zasad testowania jednostkowego - testy powinny być czytelne, szybkie i niewymagające konfiguracji, co może oznaczać użycie wszystkich prawdziwych klas.

Ogólnie możesz mieć więcej niż jeden kod pośredniczący w swoim teście, ale powinieneś mieć tylko jeden próbny. Dzieje się tak, ponieważ celem mocka jest weryfikacja zachowania, a twój test powinien testować tylko jedną rzecz.

Prosty scenariusz z użyciem C # i Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

W powyższym przykładzie użyłem Moq do zademonstrowania stubów i mocków. Moq używa tej samej klasy dla obu - Mock<T>co sprawia, że ​​jest to nieco zagmatwane. Niezależnie od tego, w czasie wykonywania test zakończy się niepowodzeniem, jeśli output.Writenie zostanie wywołany z danymi as parameter, natomiast niepowodzenie wywołania input.Read()nie zakończy się niepowodzeniem.

Igor Zevaka
źródło
4

Jak sugerowała inna odpowiedź poprzez odsyłacz do „ Mocks to not Stubs ”, makiety są formą „testowego podwójnego” używanego zamiast prawdziwego obiektu. To, co odróżnia je od innych form podwójnych testów, takich jak obiekty pośrednie, polega na tym, że inne podwójne testy testowe oferują weryfikację stanu (i opcjonalnie symulację), podczas gdy makiety oferują weryfikację zachowania (i opcjonalnie symulację).

Za pomocą kodu pośredniczącego możesz wywołać kilka metod na kodeksie w dowolnej kolejności (lub nawet w sposób powtarzalny) i określić powodzenie, jeśli kod pośredniczący przechwycił zamierzoną wartość lub stan. Natomiast obiekt pozorowany oczekuje wywołania bardzo specyficznych funkcji, w określonej kolejności, a nawet określoną liczbę razy. Test z pozorowanym obiektem zostanie uznany za „nieudany” po prostu dlatego, że metody zostały wywołane w innej kolejności lub liczniku - nawet jeśli pozorowany obiekt miał poprawny stan po zakończeniu testu!

W ten sposób pozorowane obiekty są często uważane za ściślej powiązane z kodem SUT niż obiekty pośredniczące. To może być dobre lub złe, w zależności od tego, co próbujesz zweryfikować.

Brent Arias
źródło
3

Częścią sensu używania obiektów pozorowanych jest to, że nie muszą one być tak naprawdę implementowane zgodnie ze specyfikacją. Mogą po prostu udzielić fałszywych odpowiedzi. Np. Jeśli musisz zaimplementować komponenty A i B i oba „wywołują” (współdziałają) ze sobą, to nie możesz testować A, dopóki B nie zostanie zaimplementowane i odwrotnie. W programowaniu opartym na testach jest to problem. Więc tworzysz pozorowane („fikcyjne”) obiekty dla A i B, które są bardzo proste, ale dają jakąś odpowiedź, gdy wchodzą w interakcję. W ten sposób możesz zaimplementować i przetestować A przy użyciu obiektu pozorowanego dla B.

LarsH
źródło
1

Dla php i phpunit jest dobrze wyjaśnione w dokumentacji phpunit. zobacz tutaj dokumentację phpunit

W prostych słowach obiekt mockujący jest po prostu fikcyjnym obiektem twojego oryginalnego i zwraca swoją zwracaną wartość, ta zwracana wartość może być użyta w klasie testowej

Gautam Rai
źródło
0

To jedna z głównych perspektyw testów jednostkowych. tak, próbujesz przetestować swoją pojedynczą jednostkę kodu, a wyniki testu nie powinny mieć związku z zachowaniem innych komponentów bean lub obiektów. więc powinieneś kpić z nich, używając obiektów Mock z pewną uproszczoną odpowiedzią.

Mohsen Msr
źródło