TDD Mock weryfikacja połączenia próbnego - czy to anty-wzór?

11

Robię TDD od roku, czuję się z tym całkiem nieźle, uwielbiam moje zestawy testowe i wszystko inne. Zauważyłem jednak, że ostatnio przeprowadzałem wiele próbnych weryfikacji połączeń. Na przykład miałbym usługę, do której wstrzyknie się repozytorium - w moim teście jednostkowym zdałem próbę repozytorium i sprawdziłem, czy została wywołana w ramach metody, którą testuję. Następnie sprawdziłbym, czy zwrócone wyniki są prawidłowe (w innym teście). To zdecydowanie „wydaje się” błędne, ponieważ moje testy jednostkowe są teraz bardzo powiązane ze szczegółami implementacji. Słyszałem, że powinieneś przetestować „zachowanie”, jednak w wielu sytuacjach to ... emm - niemożliwe? Jeśli maszvoidna przykład zwykle testujesz efekty uboczne. Mam na myśli, że łatwo jest iść naprzód i pokazać kilka prostych kata kodu, gdzie można to zademonstrować, ale IMHO nie odzwierciedla zbyt dobrze programów, które piszemy w prawdziwym świecie. Czy to co robię źle? Czy ten rodzaj testowania jest rodzajem anty-wzorca? Doceniam twoją opinię na ten temat, wciąż jestem trochę początkującym, jeśli chodzi o TDD.

Dimitar Dimitrow
źródło
2
Krótka odpowiedź: tak. Gdzieś tutaj są bardzo interesujące pytania na ten temat. Testy jednostkowe nie powinny być kruche i zależą w dużym stopniu od implementacji. Właśnie dlatego są testy wyższego poziomu (integracja itp.). Tutaj: programmers.stackexchange.com/questions/198453/…
Kemoda
@Kemoda Doceniłbym to, jeśli możesz połączyć mnie z dyskusją lub innym materiałem na ten temat, bardzo chciałbym ulepszyć swoje techniki.
Dimitar Dimitrov
1
masz to na przykład programmers.stackexchange.com/questions/198453 /... później znajdę inne linki
Kemoda

Odpowiedzi:

8

Powinieneś próbować testować wejścia i wyjścia. Powinieneś weryfikować zachowanie widoczne z zewnątrz. „Obietnice” lub „kontrakty”, które składa twoja klasa.

Jednocześnie czasami nie ma lepszego sposobu na przetestowanie metody niż zrobienie tego, co powiedziałeś.

Myślę, że to sprawia, że ​​twój test jest bardziej kruchy, więc powinieneś unikać testów, które opierają się na szczegółach implementacji, jeśli możesz, ale nie jest to umowa typu wszystko albo nic. Czasami jest OK, najgorsze, co się dzieje, to zmiana implementacji i aktualizacja testu.

M. Dudley
źródło
2

Celem testu jest ograniczenie możliwych produktywnych wdrożeń. Upewnij się, że nakładasz ograniczenia tylko na implementację, której naprawdę potrzebujesz. Zazwyczaj jest to , co Twój program powinien zrobić, a nie , jak to robi.

Jeśli więc na przykład twoja usługa doda coś do repozytorium, powinieneś przetestować, czy nowy wpis jest później zawarty w repozytorium, a nie, czy uruchomiono akcję dodawania.

Aby to zadziałało, musisz mieć możliwość użycia implementacji repozytorium (przetestowanej w innym miejscu) w teście usługi. Przekonałem się, że zastosowanie rzeczywistej implementacji współpracownika jest ogólnie dobrym podejściem - ponieważ jest to naprawdę najlepsza implementacja.


„No ale co, jeśli użycie rzeczywistych implementacji w teście jest drogie (np. Ponieważ wymagają zasobów, których konfiguracja jest skomplikowana)? W tym przypadku muszę użyć próbnych, prawda?”

W każdym razie prawdopodobnie chcesz jednego testu integracji, który sprawdza, czy rzeczywiste implementacje działają razem. Upewnij się, że ten jeden test integracji to wszystko, co jest potrzebne do przetestowania usługi. Innymi słowy: jeśli usługa łączy wielu współpracowników (i dlatego jest potencjalnie trudna do przetestowania), upewnij się, że nie zawiera żadnej logiki. Jeśli tak, i potrzebujesz wielu testów (integracyjnych), musisz zmienić strukturę kodu, np. Izolując logikę, a tym samym czyniąc ją bardziej testowalną.

Użycie w tym przypadku próbnych elementów łagodzi ból związany z testowaniem kawałka źle wyodrębnionej logiki, a zatem ukrywa problem architektoniczny . Więc nie używaj próbnych testów do testowania źle ustrukturyzowanego kodu, ale zamiast tego napraw strukturę.

oberlies
źródło
1
Rozumiem co mówisz. Ten temat jest nieco mylący co do tego, „ile testowania to za dużo testowania”, powiedzmy, że mam „usługę agregującą”, która zasadniczo jest fasadą i po prostu „skleja” ze sobą kilka innych usług / repozytoriów / komponentów, jakie testy piszesz za to? Mogę tylko wymyślić weryfikację połączenia. Mam nadzieję, że mam sens.
Dimitar Dimitrov
2

Moje przemyślenia dotyczą: „usług zagregowanych”.

Weryfikacja połączenia to zrobi, ale nie zapewni dużej wartości. Sprawdzasz tylko swoje okablowanie.

Istnieją 3, niewyłączne, inne sposoby:

  1. Rozszerz testy, które masz dla każdej usługi, aby sprawdzała zachowanie wyższego poziomu. Na przykład, jeśli uderzasz w bazę danych w pamięci podczas testów jednostkowych usługi, podnieś ją, aby testować usługę pod kątem rzeczywistej db. Warstwa usługi znajduje się wyżej w drzewie abstrakcyjnym, podobnie jak test.

  2. Użyj generowania kodu, aby utworzyć usługę bezpośrednio z usług zagregowanych.

  3. Użyj tego samego rodzaju refleksji lub dynamicznego języka, aby zrobić to samo. Na przykład w Javie może być możliwe użycie świetnego interfejsu, który przekazuje wywołanie bezpośrednio.

Prawdopodobnie istnieją inne sposoby, aby to zrobić, ale samo sprawdzenie okablowania ma bardzo niską opłacalność i utrudni podłączenie do tej implementacji.

Andrew Oxenburgh
źródło