Mam podstawową wiedzę mock i fałszywych obiektów, ale nie jestem pewien, mam przeczucie kiedy / gdzie używać szyderczy - zwłaszcza, że to stosuje się do tego scenariusza tutaj .
unit-testing
language-agnostic
mocking
Esteban Araya
źródło
źródło
Odpowiedzi:
Test jednostkowy powinien testować pojedynczą ścieżkę kodową za pomocą jednej metody. Kiedy wykonanie metody przechodzi poza tę metodę, do innego obiektu i z powrotem, istnieje zależność.
Testując tę ścieżkę kodu z rzeczywistą zależnością, nie wykonujesz testów jednostkowych; jesteście testami integracyjnymi. Chociaż jest to dobre i konieczne, nie jest to testowanie jednostkowe.
Jeśli Twoja zależność jest błędna, może to wpłynąć na wynik testu w taki sposób, że zwróci fałszywie dodatni wynik. Na przykład możesz przekazać zależności nieoczekiwaną wartość null, a zależność może nie zostać wyrzucona na wartość null, jak zostało udokumentowane. Twój test nie napotyka wyjątku zerowego argumentu, tak jak powinien, i test kończy się pomyślnie.
Ponadto może być trudne, jeśli nie niemożliwe, niezawodne spowodowanie, aby obiekt zależny zwrócił dokładnie to, co chcesz podczas testu. Obejmuje to również rzucanie oczekiwanych wyjątków w testach.
Mock zastępuje tę zależność. Ustawiasz oczekiwania dotyczące wywołań obiektu zależnego, ustawiasz dokładne wartości zwracane, które powinny Ci dać, aby wykonać żądany test i / lub jakie wyjątki zgłosić, aby móc przetestować kod obsługi wyjątków. W ten sposób można łatwo przetestować dane urządzenie.
TL; DR: Mock każdą zależność, której dotyka twój test jednostkowy.
źródło
Obiekty pozorowane są przydatne, gdy chcesz przetestować interakcje między testowaną klasą a określonym interfejsem.
Na przykład chcemy przetestować
sendInvitations(MailServer mailServer)
wywołania tej metodyMailServer.createMessage()
dokładnie raz, a także wywołaniaMailServer.sendMessage(m)
dokładnie raz, a żadne inne metody nie są wywoływane wMailServer
interfejsie. Wtedy możemy użyć pozorowanych obiektów.Mockując obiekty, zamiast zdawać rzeczywisty
MailServerImpl
lub testTestMailServer
, możemy przekazać symulowaną implementacjęMailServer
interfejsu. Zanim przejdziemy do makietyMailServer
, „trenujemy” ją, aby wiedziała, jakiej metody się spodziewać i jakie wartości zwracane. Na koniec obiekt pozorowany stwierdza, że wszystkie oczekiwane metody zostały wywołane zgodnie z oczekiwaniami.W teorii brzmi to dobrze, ale są też pewne wady.
Pozorowane niedociągnięcia
Jeśli masz na miejscu makietę frameworka, kusi Cię używanie obiektu mock za każdym razem , gdy musisz przekazać interfejs do testowanej klasy. W ten sposób kończysz testowanie interakcji, nawet jeśli nie jest to konieczne . Niestety, niechciane (przypadkowe) testowanie interakcji jest złe, ponieważ wtedy testujesz, że dane wymaganie jest zaimplementowane w określony sposób, zamiast tego, że implementacja dała wymagany wynik.
Oto przykład w pseudokodzie. Załóżmy, że stworzyliśmy
MySorter
klasę i chcemy ją przetestować:(W tym przykładzie zakładamy, że nie chcemy testować określonego algorytmu sortowania, takiego jak sortowanie szybkie; w takim przypadku ten drugi test byłby faktycznie prawidłowy).
W tak skrajnym przykładzie jest oczywiste, dlaczego ten drugi przykład jest błędny. Kiedy zmieniamy implementację
MySorter
, pierwszy test wykonuje świetną robotę, upewniając się, że nadal poprawnie sortujemy, co jest celem testów - pozwalają nam one bezpiecznie zmieniać kod. Z drugiej strony ten ostatni test zawsze się psuje i jest aktywnie szkodliwy; utrudnia refaktoryzację.Mocks as stubs
Mock frameworki często pozwalają również na mniej rygorystyczne użycie, gdzie nie musimy dokładnie określać, ile razy metody powinny być wywoływane i jakich parametrów się oczekuje; pozwalają na tworzenie pozorowanych obiektów, które są używane jako kody pośredniczące .
Załóżmy, że mamy metodę
sendInvitations(PdfFormatter pdfFormatter, MailServer mailServer)
, którą chcemy przetestować.PdfFormatter
Obiekt może być wykorzystywane do tworzenia zaproszenie. Oto test:W tym przykładzie tak naprawdę nie obchodzi nas
PdfFormatter
obiekt, więc po prostu trenujemy go, aby po cichu akceptował każde wywołanie i zwracał pewne rozsądne wartości zwracane w puszkach dla wszystkich metod, któresendInvitation()
akurat wywołują w tym momencie. Jak wymyśliliśmy dokładnie tę listę metod treningu? Po prostu uruchomiliśmy test i dodawaliśmy metody aż do pomyślnego zakończenia testu. Zauważ, że wytrenowaliśmy kod pośredniczący, aby odpowiadał na metodę bez pojęcia, dlaczego musi ją wywoływać, po prostu dodaliśmy wszystko, na co test narzekał. Cieszymy się, test przeszedł pomyślnie.Ale co dzieje się później, gdy zmienimy
sendInvitations()
lub inną klasę, którasendInvitations()
używa, do tworzenia bardziej fantazyjnych plików PDF? Nasz test nagle kończy się niepowodzeniem, ponieważ terazPdfFormatter
wywoływanych jest więcej metod i nie wytrenowaliśmy naszego kodu, aby ich oczekiwać. Zwykle nie jest to tylko jeden test, który kończy się niepowodzeniem w takich sytuacjach, ale każdy test, który używa, bezpośrednio lub pośrednio,sendInvitations()
metody. Musimy naprawić wszystkie te testy, dodając więcej szkoleń. Zauważ również, że nie możemy usunąć metod, które nie są już potrzebne, ponieważ nie wiemy, które z nich nie są potrzebne. Znowu utrudnia to refaktoryzację.Również czytelność testu bardzo ucierpiała, jest tam dużo kodu, którego nie napisaliśmy, ponieważ chcieliśmy, ale ponieważ musieliśmy; to nie my chcemy tam tego kodu. Testy wykorzystujące pozorowane obiekty wyglądają na bardzo złożone i często są trudne do odczytania. Testy powinny pomóc czytelnikowi zrozumieć, w jaki sposób należy korzystać z klasy będącej przedmiotem testu, dlatego powinny być proste i zrozumiałe. Jeśli nie są czytelne, nikt nie będzie ich konserwował; w rzeczywistości łatwiej je usunąć niż je utrzymywać.
Jak to naprawić? Z łatwością:
PdfFormatterImpl
. Jeśli nie jest to możliwe, zmień prawdziwe klasy, aby było to możliwe. Brak możliwości użycia klasy w testach zwykle wskazuje na pewne problemy z klasą. Naprawianie problemów to sytuacja, w której wszyscy wygrywają - naprawiłeś klasę i masz prostszy test. Z drugiej strony, nie naprawianie go i używanie makiet jest sytuacją bez wyjścia - nie naprawiłeś prawdziwej klasy i masz bardziej złożone, mniej czytelne testy, które utrudniają dalsze refaktoryzacje.TestPdfFormatter
, który nic nie robi. W ten sposób możesz zmienić to raz dla wszystkich testów, a twoje testy nie są zaśmiecone długimi konfiguracjami, w których trenujesz swoje kody.Podsumowując, pozorowane obiekty mają swoje zastosowanie, ale jeśli nie są używane ostrożnie, często zachęcają do złych praktyk, testowania szczegółów implementacji, utrudniają refaktoryzację i tworzą trudne do odczytania i trudne do utrzymania testy .
Aby uzyskać więcej informacji na temat niedociągnięć makiet, zobacz także Mock Objects: Shortcomings and Use Cases .
źródło
Praktyczna zasada:
Jeśli testowana funkcja wymaga skomplikowanego obiektu jako parametru i trudno byłoby po prostu utworzyć instancję tego obiektu (jeśli, na przykład, próbuje nawiązać połączenie TCP), użyj makiety.
źródło
Powinieneś mockować obiekt, jeśli masz zależność w jednostce kodu, którą próbujesz przetestować, która musi być „właśnie taka”.
Na przykład, gdy próbujesz przetestować jakąś logikę w swojej jednostce kodu, ale musisz uzyskać coś z innego obiektu, a to, co zostanie zwrócone z tej zależności, może wpłynąć na to, co próbujesz przetestować - mock ten obiekt.
Świetny podcast na ten temat można znaleźć tutaj
źródło