Masz klasę X i piszesz testy jednostkowe, które weryfikują zachowanie X1. Istnieje również klasa A, która bierze X jako zależność.
Pisząc testy jednostkowe dla A, kpisz z X. Innymi słowy, podczas testowania jednostkowego A ustawiasz (postulujesz) zachowanie próbnego X na X1. Czas płynie, ludzie używają twojego systemu, potrzebuje zmian, X ewoluuje: modyfikujesz X, aby pokazać zachowanie X2. Oczywiście testy jednostkowe dla X nie powiodą się i będziesz musiał je dostosować.
Ale co z A? Testy jednostkowe dla A nie zakończą się niepowodzeniem, gdy zachowanie X zostanie zmodyfikowane (z powodu drwiny z X). Jak wykryć, że wynik A będzie różny, gdy zostanie uruchomiony z „prawdziwym” (zmodyfikowanym) X?
Oczekuję odpowiedzi w stylu: „To nie jest cel testowania jednostkowego”, ale jaką wartość ma testowanie jednostkowe? Czy to naprawdę mówi ci tylko, że kiedy wszystkie testy zakończą się pomyślnie, nie wprowadziłeś przełomowej zmiany? A gdy zachowanie niektórych klas zmienia się (dobrowolnie lub niechętnie), jak możesz (najlepiej w sposób zautomatyzowany) wykryć wszystkie konsekwencje? Czy nie powinniśmy koncentrować się bardziej na testach integracyjnych?
źródło
X1
mówisz, żeX
implementuje interfejsX1
. Jeśli zmienić interfejsX1
doX2
makiety użytego w innych badaniach nie powinna już kompilacji, stąd jesteś zmuszony rozwiązać te testy też. Zmiany w zachowaniu klasy nie powinny mieć znaczenia. W rzeczywistości twoja klasaA
nie powinna zależeć od szczegółów implementacji (co w takim przypadku miałbyś zmienić). Testy jednostkoweA
są więc nadal poprawne i mówią, żeA
działa, biorąc pod uwagę idealną implementację interfejsu.Odpowiedzi:
Czy ty? Ja nie, chyba że absolutnie muszę. Muszę, jeśli:
X
jest wolny lubX
ma skutki uboczneJeśli żadne z nich nie ma zastosowania, moje testy jednostkowe również
A
zostaną przetestowaneX
. Robienie czegokolwiek innego oznaczałoby doprowadzenie testów izolujących do nielogicznej skrajności.Jeśli masz części kodu za pomocą próbnych fragmentów kodu, zgodzę się: jaki jest sens takich testów jednostkowych? Więc nie rób tego. Pozwól, aby te testy wykorzystywały rzeczywiste zależności, ponieważ w ten sposób tworzą o wiele bardziej wartościowe testy.
A jeśli niektórzy ludzie denerwują się tym, że nazywasz te testy „testami jednostkowymi”, po prostu nazywaj je „testami automatycznymi” i zacznij pisać dobre testy automatyczne.
źródło
Potrzebujesz obu. Testy jednostkowe w celu zweryfikowania zachowania każdej z jednostek oraz kilka testów integracyjnych w celu upewnienia się, że są one prawidłowo połączone. Problem polegający na poleganiu wyłącznie na testach integracyjnych polega na eksplozji kombinatorycznej wynikającej z interakcji między wszystkimi jednostkami.
Załóżmy, że masz klasę A, która wymaga 10 testów jednostkowych, aby w pełni pokryć wszystkie ścieżki. Następnie masz inną klasę B, która również wymaga 10 testów jednostkowych, aby objąć wszystkie ścieżki, które kod może przez nią przejść. Powiedzmy teraz, że w twojej aplikacji musisz podać dane wyjściowe A do B. Teraz twój kod może pobierać 100 różnych ścieżek od danych wejściowych A do danych wyjściowych B.
W przypadku testów jednostkowych potrzebujesz tylko 20 testów jednostkowych + 1 test integracyjny, aby całkowicie objąć wszystkie przypadki.
Dzięki testom integracyjnym potrzebujesz 100 testów, aby objąć wszystkie ścieżki kodu.
Oto bardzo dobre wideo na temat wad polegających na testach integracyjnych. Tylko zintegrowane testy JB Rainsberger Are A Scam HD
źródło
Łał, poczekaj chwilę. Implikacje testów dla niepowodzenia X są zbyt ważne, aby można je było tak przefiltrować.
Jeśli zmiana implementacji X z X1 na X2 psuje testy jednostkowe dla X, oznacza to, że dokonałeś wstecz niezgodnej zmiany w kontrakcie X.
X2 nie jest X, w sensie Liskowa , więc powinieneś pomyśleć o innych sposobach zaspokojenia potrzeb swoich interesariuszy (np. Wprowadzenie nowej specyfikacji Y, która jest implementowana przez X2).
Aby uzyskać głębsze informacje, zobacz Pieter Hinjens: The End of Software Versions lub Rich Hickey Simple Made Easy .
Z punktu widzenia A istnieje warunek, że współpracownik przestrzega kontraktu X. Twoja obserwacja jest efektywna, że izolowany test dla A nie daje żadnej pewności, że A rozpoznaje współpracowników, którzy naruszają umowę X.
Recenzja zintegrowanych testów to oszustwo ; na wysokim poziomie oczekuje się, że będziesz mieć tyle izolowanych testów, ile potrzebujesz, aby upewnić się, że X2 poprawnie implementuje kontrakt X, oraz tyle izolowanych testów, ile potrzebujesz, aby upewnić się, że A robi właściwą rzecz, biorąc pod uwagę interesujące odpowiedzi od X, i pewna mniejsza liczba zintegrowanych testów, aby upewnić się, że X2 i A zgadzają się co do tego, co oznacza X.
Czasami zobaczysz to rozróżnienie wyrażone jako samotne testy kontra
sociable
testy; patrz Jay Fields współpracujący skutecznie z testami jednostkowymi .Ponownie zobacz, jak zintegrowane testy to oszustwo - Rainsberger szczegółowo opisuje pozytywną pętlę sprzężenia zwrotnego, która jest powszechna (w jego doświadczeniach) w projektach opartych na zintegrowanych testach (pisownia notatek). Podsumowując, bez izolowanych / pojedynczych testów wywierających presję na projekt , jakość pogarsza się, prowadząc do większej liczby błędów i bardziej zintegrowanych testów ...
Będziesz także potrzebował (niektórych) testów integracyjnych. Oprócz złożoności wprowadzonej przez wiele modułów, wykonywanie tych testów ma zwykle większy opór niż testy pojedyncze; bardziej efektywne jest iterowanie bardzo szybkich kontroli, gdy praca jest w toku, zapisywanie dodatkowych kontroli, gdy uważasz, że jesteś „skończony”.
źródło
Zacznę od stwierdzenia, że podstawowa przesłanka pytania jest błędna.
Nigdy nie testujesz (ani nie wyśmiewasz) implementacji, testujesz (i wyśmiewasz) interfejsy .
Jeśli mam prawdziwą klasę X, która implementuje interfejs X1, mogę napisać próbny XM, który również jest zgodny z X1. Następnie moja klasa A musi użyć czegoś, co implementuje X1, którym może być albo klasa X, albo sztuczny XM.
Załóżmy teraz, że zmienimy X, aby wprowadzić nowy interfejs X2. Oczywiście mój kod już się nie kompiluje. Wymaga czegoś, co implementuje X1 i które już nie istnieje. Problem został zidentyfikowany i można go naprawić.
Załóżmy, że zamiast zastąpić X1, po prostu go modyfikujemy. Teraz klasa A jest już ustawiona. Jednak sztuczny XM nie implementuje już interfejsu X1. Problem został zidentyfikowany i można go naprawić.
Cała podstawa do testowania jednostkowego i drwiny polega na tym, że piszesz kod korzystający z interfejsów. Konsumenta interfejsu nie obchodzi sposób implementacji kodu, tylko przestrzeganie tej samej umowy (wejścia / wyjścia).
To się psuje, gdy twoje metody mają skutki uboczne, ale myślę, że można to bezpiecznie wykluczyć, ponieważ „nie można przetestować ani wyśmiewać”.
źródło
Kolejne pytania:
Pisanie i uruchamianie jest tanie, a Ty otrzymujesz wczesną informację zwrotną. Jeśli złamiesz X, od razu dowiesz się, czy masz dobre testy. Nawet nie rozważ pisania testów integracyjnych, chyba że przetestowałeś wszystkie warstwy (tak, nawet w bazie danych).
Po zdaniu testów może ci powiedzieć niewiele. Być może nie masz wystarczającej liczby testów. Być może nie przetestowałeś wystarczającej liczby scenariuszy. Pokrycie kodu może tu pomóc, ale nie jest to srebrna kula. Możesz mieć testy, które zawsze przechodzą. Stąd czerwony jest często pomijanym pierwszym krokiem czerwonego, zielonego reaktora.
Więcej testów - chociaż narzędzia stają się coraz lepsze. ALE powinieneś definiować zachowanie klasy w interfejsie (patrz poniżej). Uwaga: zawsze będzie miejsce do ręcznego testowania na szczycie piramidy testowej.
Coraz więcej testów integracyjnych też nie jest odpowiedzią, są drogie w pisaniu, uruchamianiu i utrzymaniu. W zależności od konfiguracji kompilacji menedżer kompilacji może je i tak wykluczyć, czyniąc je zależnymi od programisty pamiętającego (nigdy nie jest to dobra rzecz!).
Widziałem, jak programiści spędzają godziny próbując naprawić zepsute testy integracyjne, które znaleźliby w ciągu pięciu minut, gdyby mieli dobre testy jednostkowe. Jeśli to się nie powiedzie, spróbuj po prostu uruchomić oprogramowanie - to wszystko, o co dbają Twoi użytkownicy końcowi. Nie ma sensu mieć miliona testów jednostkowych, które przejdą, jeśli cały zestaw kart upadnie, gdy użytkownik uruchomi cały pakiet.
Jeśli chcesz mieć pewność, że klasa A zużywa klasę X w ten sam sposób, powinieneś używać interfejsu zamiast konkrecji. Wtedy przełomowa zmiana jest bardziej prawdopodobna w momencie kompilacji.
źródło
To jest poprawne.
Testy jednostkowe mają na celu przetestowanie izolowanej funkcjonalności jednostki, sprawdzenie na pierwszy rzut oka, czy działa ona zgodnie z przeznaczeniem i nie zawiera głupich błędów.
Testy jednostkowe nie służą do sprawdzenia działania całej aplikacji.
Wiele osób zapomina, że testy jednostkowe są najszybszym i najbrudniejszym sposobem sprawdzania poprawności kodu. Kiedy już wiesz, że twoje małe procedury działają, musisz także przeprowadzić testy integracji. Samo testowanie jednostkowe jest tylko nieznacznie lepsze niż brak testowania.
W ogóle mamy testy jednostkowe, ponieważ powinny być tanie. Szybkie tworzenie, uruchamianie i konserwacja. Gdy zaczniesz przekształcać je w min. Testy integracyjne, wkroczysz w świat bólu. Równie dobrze możesz przejść pełny test integracji i całkowicie zignorować testy jednostkowe, jeśli zamierzasz to zrobić.
teraz niektórzy ludzie myślą, że jednostka nie jest tylko funkcją w klasie, ale całą klasą (w tym mną). Wszystko to jednak powoduje zwiększenie rozmiaru jednostki, więc może być konieczne mniej testów integracyjnych, ale nadal jest to potrzebne. Nadal niemożliwe jest sprawdzenie, czy Twój program robi to, co powinien, bez pełnego pakietu testów integracji.
a następnie nadal musisz przeprowadzić pełne testy integracyjne w systemie na żywo (lub pół-na żywo), aby sprawdzić, czy działa on w warunkach, których używa klient.
źródło
Testy jednostkowe nie dowodzą poprawności czegokolwiek. Dotyczy to wszystkich testów. Zwykle testy jednostkowe są łączone z projektowaniem opartym na umowie (projekt na podstawie umowy to inny sposób, aby to powiedzieć) i ewentualnie zautomatyzowanymi dowodami poprawności, jeśli poprawność wymaga regularnej weryfikacji.
Jeśli masz rzeczywiste kontrakty, składające się z niezmienników klasy, warunków wstępnych i warunków końcowych, możliwe jest hierarchiczne udowodnienie poprawności, opierając poprawność komponentów wyższego poziomu na kontraktach komponentów niższego poziomu. To podstawowa koncepcja projektowania na podstawie umowy.
źródło
fac(5) == 120
, ty nie udowodniono, żefac()
rzeczywiście powrócić silni jej argumentu. Udowodniłeś tylko, żefac()
po przejściu zwraca silnię piątki5
. I nawet to nie jest pewne, jakfac()
to możliwe, może wrócić42
zamiast tego w pierwsze poniedziałki po całkowitym zaćmieniu w Timbuktu ... Problem polega na tym, że nie można udowodnić zgodności poprzez sprawdzenie poszczególnych danych wejściowych testu, należy sprawdzić wszystkie możliwe dane wejściowe, a także udowodnić, że niczego nie zapomniałeś (jak czytanie zegara systemowego).Uważam, że mocno wyśmiewane testy rzadko są przydatne. W większości przypadków kończę reimplementację zachowania, które ma już klasa oryginalna, co całkowicie pokonuje cel kpin.
Dla mnie lepszą strategią jest dobre rozdzielenie problemów (np. Możesz przetestować Część A swojej aplikacji bez wprowadzania części B do Z). Tak dobra architektura naprawdę pomaga napisać dobry test.
Ponadto jestem bardziej niż chętny do zaakceptowania efektów ubocznych, o ile mogę je wycofać, np. Jeśli moja metoda modyfikuje dane w db, pozwól mu! Tak długo, jak mogę przywrócić db do poprzedniego stanu, jaka szkoda? Zaletą mojego testu jest sprawdzenie, czy dane wyglądają zgodnie z oczekiwaniami. DB w pamięci lub określone testowe wersje dbs naprawdę tu pomagają (np. Wersja testowa RavenDB w pamięci).
Wreszcie lubię robić drwiny na granicach usługi, np. Nie wykonuj tego połączenia HTTP z usługą b, ale przechwyćmy to i wprowadźmy odpowiednie
źródło
Chciałbym, aby ludzie w obu obozach zrozumieli, że testy klasowe i testy zachowania nie są ortogonalne.
Testy klasowe i testy jednostkowe są stosowane zamiennie i być może nie powinny. Niektóre testy jednostkowe są po prostu zaimplementowane w klasach. To wszystko. Testy jednostkowe odbywały się od dziesięcioleci w językach bez zajęć.
Jeśli chodzi o testowanie zachowań, można to zrobić w ramach testów klasowych przy użyciu konstruktu GWT.
Ponadto to, czy zautomatyzowane testy przebiegają zgodnie z klasą czy zachowaniem, zależy raczej od twoich priorytetów. Niektórzy będą musieli szybko prototypować i wyciągnąć coś za drzwi, podczas gdy inni będą mieli ograniczenia zasięgu z powodu stylów domowych. Wiele powodów. Oba są całkowicie poprawnymi podejściami. Płacisz pieniądze, wybierasz.
Co więc zrobić, gdy kod się zepsuje. Jeśli został zakodowany w interfejsie, tylko konkrecja musi się zmienić (wraz z wszelkimi testami).
Jednak wprowadzenie nowego zachowania wcale nie musi narażać systemu na szwank. Linux i in. Są pełne przestarzałych funkcji. Rzeczy takie jak konstruktory (i metody) mogą być przeciążone bez zmuszania całego kodu wywołującego do zmiany.
Tam, gdzie wygrywa testowanie klas, musisz wprowadzić zmiany w klasie, w której jeszcze nie było kanalizacji (z powodu ograniczeń czasowych, złożoności itp.). O wiele łatwiej jest zacząć z klasą, jeśli ma kompleksowe testy.
źródło
O ile interfejs X nie zostanie zmieniony, nie trzeba zmieniać testu jednostkowego dla A, ponieważ nic się nie zmieniło dla A. Wygląda na to, że naprawdę napisałeś razem test jednostkowy X i A, ale nazwałeś to testem jednostkowym A:
Idealnie, próbka X powinna symulować wszystkie możliwe zachowania X, a nie tylko zachowanie oczekiwane od X. Więc bez względu na to, co faktycznie zaimplementujesz w X, A powinien już być w stanie sobie z tym poradzić. Zatem żadna zmiana na X, oprócz zmiany samego interfejsu, nie będzie miała żadnego wpływu na test jednostkowy dla A.
Na przykład: Załóżmy, że A jest algorytmem sortowania, a A zapewnia dane do posortowania. Makieta X powinna zapewnić zerową wartość zwracaną, pustą listę danych, pojedynczy element, wiele elementów już posortowanych, wiele elementów jeszcze nie posortowanych, wiele elementów posortowanych wstecz, listy z powtórzeniem tego samego elementu, wartości null zmieszane, śmiesznie duża liczba elementów, a także powinien rzucić wyjątek.
Być może X początkowo zwrócił posortowane dane w poniedziałki i puste listy we wtorki. Ale teraz, gdy X zwraca nieposortowane dane w poniedziałki i zgłasza wyjątki we wtorki, A nie ma znaczenia - te scenariusze zostały już uwzględnione w teście jednostkowym A.
źródło
Musisz spojrzeć na różne testy.
Same testy jednostkowe będą testować tylko X. Są one po to, aby zapobiec zmianie zachowania X, ale nie zabezpieczyć całego systemu. Zapewniają, że możesz refaktoryzować swoją klasę bez wprowadzania zmian w zachowaniu. A jeśli złamiesz X, złamiesz go ...
Powinien rzeczywiście mock X dla swoich testów jednostkowych i testy z Mock powinien zachować przechodząc nawet po jego zmianie.
Ale jest więcej niż jeden poziom testowania! Istnieją również testy integracyjne. Testy te służą do sprawdzenia interakcji między klasami. Te testy zwykle mają wyższą cenę, ponieważ nie używają próbnych elementów do wszystkiego. Na przykład test integracyjny może faktycznie zapisać rekord w bazie danych, a test jednostkowy nie powinien mieć żadnych zewnętrznych zależności.
Również, jeśli X musi mieć nowe zachowanie, lepiej byłoby podać nową metodę, która zapewniłaby pożądany wynik
źródło