Kiedy wykonuję testy jednostkowe w „właściwy” sposób, tj. Przerywając każde publiczne połączenie i zwracając ustawione wartości lub kpiny, mam wrażenie, że tak naprawdę niczego nie testuję. Dosłownie patrzę na mój kod i tworzę przykłady oparte na logice za pomocą moich publicznych metod. I za każdym razem, gdy zmienia się implementacja, muszę iść i zmieniać te testy, ponownie, nie czując, że osiągam coś użytecznego (czy to w perspektywie średnio-, czy długoterminowej). Robię również testy integracyjne (w tym ścieżki nie-szczęśliwe) i nie mam nic przeciwko wydłużonym czasom testowania. W przypadku tych mam wrażenie, że faktycznie testuję pod kątem regresji, ponieważ wykryli wiele, podczas gdy wszystkie testy jednostkowe pokazują, że zmieniła się implementacja mojej publicznej metody, o której już wiem.
Testy jednostkowe to rozległy temat i wydaje mi się, że nie rozumiem tutaj czegoś. Jaka jest decydująca zaleta testów jednostkowych w porównaniu z testami integracyjnymi (wyłączając czasochłonność)?
źródło
Odpowiedzi:
Wygląda na to, że metoda, którą testujesz, potrzebuje kilku innych instancji klas (które musisz wyśmiewać) i sama wywołuje kilka metod.
Ten rodzaj kodu jest rzeczywiście trudny do testowania jednostkowego z powodów, które przedstawisz.
Pomocne jest podzielenie takich klas na:
Następnie klasy od 1. są łatwe do testowania jednostkowego, ponieważ akceptują tylko wartości i zwracają wynik. W bardziej skomplikowanych przypadkach klasy te mogą wymagać samodzielnego wykonywania wywołań, ale będą wywoływać klasy tylko z 2. (a nie bezpośrednio wywoływać np. Funkcję bazy danych), a klasy z 2. są łatwe do wyśmiewania (ponieważ tylko ujawnij potrzebne części owiniętego systemu).
Klasy od 2. i 3. zwykle nie mogą być w znaczący sposób testowane jednostkowo (ponieważ same nie robią nic pożytecznego, są jedynie kodem „kleju”). OTOH, te klasy są zwykle stosunkowo proste (i nieliczne), więc powinny być odpowiednio objęte testami integracyjnymi.
Przykład
Jedna klasa
Załóżmy, że masz klasę, która pobiera cenę z bazy danych, stosuje pewne rabaty, a następnie aktualizuje bazę danych.
Jeśli masz to wszystko w jednej klasie, będziesz musiał wywoływać funkcje DB, które trudno wyśmiewać. W pseudokodzie:
Wszystkie trzy kroki będą wymagały dostępu do bazy danych, więc wiele (złożonych) szyderstw, które mogą ulec uszkodzeniu, jeśli zmieni się kod lub struktura bazy danych.
Rozdzielać się
Dzielisz na trzy klasy: PriceCalculation, PriceRepository, App.
PriceCalculation wykonuje tylko rzeczywiste obliczenia i otrzymuje wymagane wartości. Aplikacja wiąże wszystko razem:
W ten sposób:
Wreszcie może się okazać, że PriceCalculation musi wykonywać własne wywołania bazy danych. Na przykład, ponieważ tylko PriceCalculation wie, jakie dane potrzebuje, więc aplikacja nie może ich wcześniej pobrać. Następnie możesz przekazać instancję PriceRepository (lub inną klasę repozytorium), dostosowaną do potrzeb PriceCalculation. Klasa ta będzie musiała zostać wyśmiewana, ale będzie to proste, ponieważ interfejs PriceRepository jest prosty, np
PriceRepository.getPrice(articleNo, contractType)
. Co najważniejsze, interfejs PriceRepository izoluje PriceCalculation od bazy danych, więc zmiany schematu DB lub organizacji danych raczej nie zmienią jego interfejsu, a tym samym nie udaremnią prób.źródło
To fałszywa dychotomia.
Testy jednostkowe i integracyjne służą dwóm podobnym, ale różnym celom. Celem testów jednostkowych jest upewnienie się, że metody działają. W praktyce testy jednostkowe upewniają się, że kod spełnia warunki określone w testach jednostkowych. Jest to widoczne w sposobie projektowania testów jednostkowych: w szczególności określają, co powinien zrobić kod, i twierdzą, że kod to robi.
Testy integracyjne są różne. Testy integracyjne sprawdzają interakcję między komponentami oprogramowania. Możesz mieć komponenty oprogramowania, które przejdą wszystkie ich testy i nadal nie przejdą testów integracji, ponieważ nie działają prawidłowo.
Jeśli jednak testy jednostkowe mają decydującą przewagę, jest to następujące: testy jednostkowe są znacznie łatwiejsze do skonfigurowania i wymagają znacznie mniej czasu i wysiłku niż testy integracyjne. Przy prawidłowym stosowaniu testy jednostkowe zachęcają do opracowania kodu „testowalnego”, co oznacza, że końcowy wynik będzie bardziej niezawodny, łatwiejszy do zrozumienia i łatwiejszy do utrzymania. Testowalny kod ma pewne cechy, takie jak spójny interfejs API, powtarzalne zachowanie i zwraca wyniki, które są łatwe do potwierdzenia.
Testy integracyjne są trudniejsze i droższe, ponieważ często potrzebne są skomplikowane kpiny, złożona konfiguracja i trudne stwierdzenia. Na najwyższym poziomie integracji systemu wyobraź sobie próbę symulacji interakcji człowieka w interfejsie użytkownika. Całe systemy oprogramowania są poświęcone tego rodzaju automatyzacji. I szukamy automatyzacji; testy na ludziach nie są powtarzalne i nie skalują się tak jak testy automatyczne.
Wreszcie, testy integracyjne nie gwarantują pokrycia kodu. Ile kombinacji pętli kodu, warunków i gałęzi testujesz w testach integracyjnych? Czy naprawdę wiesz Istnieją narzędzia, których można użyć w testach jednostkowych i testowanych metodach, które podadzą, ile masz pokrycia kodu i jaka jest jego cykliczna złożoność. Ale naprawdę działają dobrze tylko na poziomie metody, w której przeprowadzane są testy jednostkowe.
Jeśli Twoje testy zmieniają się za każdym razem, gdy dokonujesz refaktoryzacji, jest to inny problem. Testy jednostkowe powinny polegać na udokumentowaniu tego, co robi twoje oprogramowanie, udowodnieniu, że to robi, a następnie udowodnieniu, że robi to ponownie, gdy refaktoryzujesz podstawową implementację. Jeśli twój interfejs API ulegnie zmianie lub potrzebujesz metod, które zmienią się zgodnie ze zmianą w projekcie systemu, tak właśnie powinno się stać. Jeśli to się często zdarza, najpierw napisz testy, zanim napiszesz kod. Zmusi cię to do zastanowienia się nad ogólną architekturą i pozwoli ci pisać kod z już ustanowionym API.
Jeśli spędzasz dużo czasu, pisząc testy jednostkowe dla takiego trywialnego kodu
powinieneś ponownie przeanalizować swoje podejście. Testowanie jednostkowe powinno sprawdzać zachowanie, aw powyższym wierszu kodu nie ma żadnego zachowania. Jednak stworzyłeś gdzieś zależność w swoim kodzie, ponieważ ta właściwość prawie na pewno będzie przywoływana gdzie indziej w twoim kodzie. Zamiast tego, zastanów się nad napisaniem metod, które akceptują potrzebną właściwość jako parametr:
Teraz twoja metoda nie ma żadnych zależności od czegoś poza sobą i jest teraz bardziej testowalna, ponieważ jest całkowicie samodzielna. To prawda, że nie zawsze będziesz w stanie to zrobić, ale przesuwa twój kod w kierunku większej możliwości testowania i tym razem piszesz test jednostkowy dla rzeczywistego zachowania.
źródło
Testy jednostkowe z próbami mają upewnić się, że implementacja klasy jest poprawna. Kpisz z publicznych interfejsów testowanych zależności kodu. W ten sposób masz kontrolę nad wszystkim, co jest poza klasą i masz pewność, że test zakończony niepowodzeniem jest spowodowany czymś wewnętrznym dla klasy, a nie jednym z innych obiektów.
Testujesz także zachowanie testowanej klasy, a nie implementację. W przypadku zmiany kodu (tworzenie nowych metod wewnętrznych itp.) Testy jednostkowe nie powinny zakończyć się niepowodzeniem. Ale jeśli zmieniasz to, co robi metoda publiczna, absolutnie testy powinny zakończyć się niepowodzeniem, ponieważ zmieniłeś zachowanie.
Brzmi również tak, jakbyś pisał testy po napisaniu kodu, spróbuj zamiast tego napisać pierwsze testy. Spróbuj opisać zachowanie, które powinna mieć klasa, a następnie napisz minimalną ilość kodu, aby testy przebiegły pomyślnie.
Testy jednostkowe i integracyjne są przydatne do zapewnienia jakości kodu. Testy jednostkowe badają każdy element oddzielnie. Testy integracyjne upewniają się, że wszystkie komponenty działają poprawnie. Chcę mieć oba typy w moim pakiecie testowym.
Testy jednostkowe pomogły mi w rozwoju, ponieważ mogę skupić się na jednej aplikacji na raz. Kpi z komponentów, których jeszcze nie stworzyłem. Świetnie nadają się również do regresji, ponieważ dokumentują wszelkie błędy w logice, które znalazłem (nawet w testach jednostkowych).
AKTUALIZACJA
Utworzenie testu, który tylko upewnia się, że metody są wywoływane, ma wartość, ponieważ upewniasz się, że metody faktycznie zostaną wywołane. W szczególności, jeśli najpierw piszesz testy, masz listę kontrolną metod, które muszą się zdarzyć. Ponieważ ten kod jest w dużej mierze proceduralny, nie musisz wiele sprawdzać poza tym, że wywoływane są metody. Chronisz kod przed zmianami w przyszłości. Kiedy musisz wywołać jedną metodę przed drugą. Lub że metoda jest zawsze wywoływana, nawet jeśli metoda początkowa zgłasza wyjątek.
Test dla tej metody może się nigdy nie zmieniać lub może się zmieniać tylko podczas zmiany metod. Dlaczego to jest złe? Pomaga wzmocnić za pomocą testów. Jeśli musisz zmienić test po zmianie kodu, będziesz miał zwyczaj zmieniać testy za pomocą kodu.
źródło
Miałem podobne pytanie - dopóki nie odkryłem mocy testów komponentów. Krótko mówiąc, są one takie same jak testy jednostkowe, z tym wyjątkiem, że domyślnie nie kpisz, ale używasz prawdziwych obiektów (najlepiej poprzez wstrzykiwanie zależności).
W ten sposób możesz szybko tworzyć solidne testy z dobrym pokryciem kodu. Nie musisz ciągle aktualizować swoich makiet. Może to być nieco mniej precyzyjne niż testy jednostkowe ze 100% próbami, ale zaoszczędzony czas i pieniądze to rekompensują. Jedyne, czego naprawdę potrzebujesz, aby użyć makiet lub urządzeń, to zaplecze pamięci lub usługi zewnętrzne.
W rzeczywistości nadmierne drwiny są anty-wzorcem: anty-wzorce TDD i makiety są złe .
źródło
Chociaż operacja już zaznaczyła odpowiedź, po prostu dodaję tutaj moje 2 centy.
A także w odpowiedzi na
Jest pomocne, ale nie dokładnie to, o co poprosił OP:
Testy jednostkowe działają, ale nadal występują błędy?
z mojego małego doświadczenia w testowaniu pakietów rozumiem, że testy jednostkowe mają zawsze sprawdzać najbardziej podstawową funkcjonalność klasy na poziomie metody. Moim zdaniem każda metoda publiczna, prywatna lub wewnętrzna zasługuje na dedykowane testy jednostkowe. Nawet w moim ostatnim doświadczeniu miałem publiczną metodę, która nazywała inną małą prywatną metodą. Były więc dwa podejścia:
Jeśli myślisz logicznie, sens posiadania prywatnej metody jest taki: główna metoda publiczna staje się zbyt duża lub bałagan. Aby rozwiązać ten problem, mądrze dokonujesz refaktoryzacji i tworzysz małe fragmenty kodu, które zasługują na osobne metody prywatne, co z kolei sprawia, że Twoja główna metoda publiczna jest mniej obszerna. Refaktoryzujesz, pamiętając, że ta prywatna metoda może być później ponownie wykorzystana. Mogą istnieć przypadki, w których nie ma innej metody publicznej w zależności od tej metody prywatnej, ale kto wie o przyszłości.
Biorąc pod uwagę przypadek, gdy metoda prywatna jest ponownie wykorzystywana przez wiele innych metod publicznych.
Tak więc, gdybym wybrał podejście 1: powieliłbym testy jednostkowe i byłyby one skomplikowane, ponieważ miałeś liczbę testów jednostkowych dla każdej gałęzi metody publicznej, a także prywatnej.
Gdybym wybrał podejście 2: kod napisany dla testów jednostkowych byłby stosunkowo mniejszy, a testowanie byłoby znacznie łatwiejsze.
Biorąc pod uwagę przypadek, gdy metoda prywatna nie jest ponownie wykorzystywana Nie ma sensu pisać osobnego testu jednostkowego dla tej metody.
Jeśli chodzi o testy integracyjne, są one zazwyczaj wyczerpujące i na wysokim poziomie. Powiedzą Ci, że biorąc pod uwagę wkład, wszystkie Twoje klasy powinny dojść do tego końcowego wniosku. Aby dowiedzieć się więcej na temat przydatności testów integracyjnych, zobacz wspomniany link.
źródło