Jakiś czas temu przeczytałem na odpowiedzi Przepełnienie stosu, której nie mogę znaleźć, zdanie wyjaśniające, że powinieneś przetestować publiczne interfejsy API, a autor powiedział, że powinieneś przetestować interfejsy. Autor wyjaśnił również, że jeśli zmieni się implementacja metody, nie trzeba modyfikować przypadku testowego, ponieważ spowoduje to zerwanie umowy zapewniającej działanie testowanego systemu. Innymi słowy, test powinien zakończyć się niepowodzeniem, jeśli metoda nie działa, ale nie dlatego, że implementacja uległa zmianie.
Zwróciło to moją uwagę, gdy mówimy o kpinach. Ponieważ wyśmiewanie polega w dużej mierze na wywołaniach oczekujących z testowanych zależności systemu, symulacje są ściśle powiązane z implementacją, a nie z interfejsem.
Podczas badań próbnych i próbnych , kilka artykułów zgadza się, że zamiast próbnych powinny być stosowane kody pośredniczące, ponieważ nie opierają się one na oczekiwaniach wynikających z zależności, co oznacza, że test nie wymaga znajomości bazowego systemu w trakcie testowania.
Moje pytania brzmiałyby:
- Czy kpiny naruszają zasadę otwartego / zamkniętego?
- Czy w ostatnim argumencie brakuje czegoś na korzyść kodów pośredniczących, które sprawiają, że kody pośredniczące nie są tak świetne w porównaniu do próbnych?
- Jeśli tak, to kiedy byłby to dobry przypadek użycia do wyszydzenia, a kiedy byłby dobry przypadek użycia do użycia kodów pośredniczących?
źródło
Since mocking relays heavily on expectation calls from system under test's dependencies...
Myślę, że właśnie tam się nie udajesz. Kpina to sztuczna reprezentacja systemu zewnętrznego. Nie reprezentuje w żaden sposób systemu zewnętrznego, z wyjątkiem przypadków, gdy symuluje system zewnętrzny w taki sposób, że pozwala na uruchomienie testów z kodem zależnym od tego systemu zewnętrznego. Nadal będziesz potrzebować testów integracyjnych, aby udowodnić, że Twój kod działa z prawdziwym, niezamkniętym systemem.Odpowiedzi:
Nie rozumiem, dlaczego drwiny naruszałyby zasadę otwartego / zamkniętego. Jeśli możesz nam wyjaśnić, dlaczego według ciebie mogą, to możemy złagodzić twoje obawy.
Jedyną wadą kodów pośredniczących, o których mogę myśleć, jest to, że na ogół wymagają więcej pracy niż pisanie próbne, ponieważ każdy z nich jest w rzeczywistości alternatywną implementacją interfejsu zależnego, więc na ogół musi zapewniać pełną (lub przekonująco kompletną) implementacja interfejsu zależnego. Aby dać ci skrajny przykład, jeśli testowany podsystem wywołuje RDBMS, wówczas próbka RDBMS po prostu odpowiada na określone zapytania, o których wiadomo, że są wysyłane przez testowany podsystem, dając z góry określone zestawy danych testowych. Z drugiej strony alternatywną implementacją byłby w pełni funkcjonalny RDBMS w pamięci, być może z dodatkowym obciążeniem związanym z koniecznością emulacji dziwactw rzeczywistego klienta-serwera RDBMS, którego używasz na produkcji. (Na szczęście mamy takie rzeczy jak HSQLDB, więc właściwie możemy to zrobić, ale nadal,
Dobrymi przykładami użycia kpienia są sytuacje, w których interfejs zależny jest zbyt skomplikowany, aby napisać dla niego alternatywną implementację lub jeśli masz pewność, że napiszesz makietę tylko raz i nigdy jej nie dotkniesz. W takich przypadkach skorzystaj z szybkiej i brudnej makiety. W związku z tym dobre przypadki użycia kodów pośredniczących (implementacje alternatywne) to właściwie wszystko inne. Zwłaszcza jeśli przewidujesz długoterminowe relacje z testowanym podsystemem, zdecydowanie wybierz alternatywną implementację, która będzie ładna i czysta i będzie wymagać konserwacji tylko w przypadku zmiany interfejsu, zamiast wymagać konserwacji za każdym razem, gdy interfejs zmiany i za każdym razem, gdy zmienia się wdrażanie testowanego podsystemu.
PS Osoba, o której mówisz, może być mną, w jednej z moich innych odpowiedzi związanych z testowaniem tutaj na programmers.stackexchange.com, na przykład tej .
źródło
an alternative implementation would be a full-blown in-memory RDBMS
- Nie musisz koniecznie iść tak daleko z kikutem.Zasada Otwarta / Zamknięta polega przede wszystkim na możliwości zmiany zachowania klasy bez modyfikacji. Dlatego wstrzyknięcie fałszywej zależności komponentu do testowanej klasy nie narusza jej.
Problem z podwójnymi testami (makiety / odgałęzienia) polega na tym, że w zasadzie przyjmujesz arbitralne założenia dotyczące tego, w jaki sposób testowana klasa oddziałuje z otoczeniem. Jeśli te oczekiwania są błędne, po wdrożeniu kodu mogą wystąpić problemy. Jeśli możesz sobie na to pozwolić, przetestuj kod w tych samych ograniczeniach, które ograniczają środowisko produkcyjne. Jeśli nie możesz, podejmij najmniejsze możliwe założenia i próbuj / stub tylko peryferia twojego systemu (baza danych, usługa uwierzytelniania, klient HTTP itp.).
Jedynym słusznym powodem, dla którego należy użyć IMHO, podwójnego, jest to, kiedy trzeba zarejestrować jego interakcje z testowaną klasą lub gdy trzeba podać fałszywe dane (co mogą zrobić obie techniki). Bądź jednak ostrożny, nadużywanie go odzwierciedla zły projekt lub test, który w zbyt dużym stopniu opiera się na testowanym interfejsie API.
źródło
Uwaga: Zakładam, że definiujesz Mock jako „klasę bez implementacji, po prostu coś, co możesz monitorować”, a Stub jako „częściową próbę, czyli używa niektórych rzeczywistych zachowań zaimplementowanej klasy”, zgodnie z tym stosem Pytanie o przepełnieniu .
Nie jestem pewien, dlaczego uważasz, że konsensus polega na użyciu kodów pośredniczących , na przykład w dokumentacji Mockito jest odwrotnie.
Ta dokumentacja mówi to lepiej niż ja. Używanie prób pozwala ci tylko przetestować jedną konkretną klasę i nic więcej; jeśli potrzebujesz częściowych prób, aby osiągnąć pożądane zachowanie, prawdopodobnie zrobiłeś coś złego, naruszasz SRP i tak dalej, a Twój kod może stać się refaktorem. Makiety nie naruszają zasady otwartego zamknięcia, ponieważ i tak są one zawsze używane tylko w testach, nie są prawdziwymi zmianami w tym kodzie. Zwykle i tak są generowane w locie przez bibliotekę taką jak cglib.
źródło
Myślę, że problem może wynikać z założenia, że jedynymi prawidłowymi testami są te, które spełniają test otwarty / zamknięty.
Łatwo zauważyć, że jedynym testem, który powinien mieć znaczenie, jest testowanie interfejsu. Jednak w rzeczywistości często bardziej efektywne jest testowanie tego interfejsu poprzez testowanie wewnętrznych mechanizmów.
Na przykład prawie niemożliwe jest przetestowanie jakichkolwiek negatywnych wymagań, takich jak „wdrożenie nie spowoduje żadnych wyjątków”. Rozważ implementację interfejsu mapy z hashapem. Chcesz mieć pewność, że mapa skrótów spełnia interfejs mapy, bez rzucania, nawet jeśli trzeba ją przerobić (co może stać się ryzykowne). Możesz przetestować każdą kombinację danych wejściowych, aby upewnić się, że spełniają one wymagania dotyczące interfejsu, ale może to potrwać dłużej niż śmierć cieplna wszechświata. Zamiast tego przerywasz nieco enkapsulację i opracowujesz symulacje, które oddziałują ściślej, zmuszając hashap do wykonania dokładnie powtórzenia potrzebnego, aby algorytm powtórzenia nie rzucił.
Tl / Dr: robienie tego „przy książce” jest fajne, ale kiedy przychodzi do popchnięcia, posiadanie produktu na biurku szefa do piątku jest bardziej przydatne niż testowy zestaw książek, który trwa do śmierci termicznej wszechświat, aby potwierdzić zgodność.
źródło