Rozumiem wartość automatycznego testowania i używam go wszędzie tam, gdzie problem jest wystarczająco dokładnie określony, że mogę wymyślić dobre przypadki testowe. Zauważyłem jednak, że niektórzy ludzie tutaj i na StackOverflow kładą nacisk na testowanie tylko jednostki, a nie jej zależności. Tutaj nie widzę korzyści.
Wyśmiewanie / odgałęzienie w celu uniknięcia zależności testowych zwiększa złożoność testów. Dodaje sztuczną elastyczność / wymagania odsprzęgania do kodu produkcyjnego w celu obsługi kpiny. (Nie zgadzam się z każdym, kto twierdzi, że promuje to dobry projekt. Pisanie dodatkowego kodu, wprowadzanie takich rzeczy, jak frameworki wstrzykiwania zależności lub w inny sposób zwiększanie złożoności bazy kodu, aby uczynić rzeczy bardziej elastycznymi / podłączanymi / rozszerzalnymi / oddzielonymi bez rzeczywistego przypadku użycia, to nadmierna inżynieria, a nie dobry projekt.)
Po drugie, testowanie zależności oznacza, że krytyczny kod niskiego poziomu, który jest używany wszędzie, jest testowany przy użyciu danych wejściowych innych niż te, które ktoś, kto napisał testy, wyraźnie myślał. Znalazłem wiele błędów w funkcjonowaniu niskiego poziomu, uruchamiając testy jednostkowe na funkcjonalności wysokiego poziomu bez wyśmiewania się z funkcji niskiego poziomu, od których była zależna. Najlepiej byłoby, gdyby testy jednostkowe wykazały funkcjonalność niskiego poziomu, ale zawsze zdarzają się przypadki pominięcia.
Jaka jest druga strona tego? Czy naprawdę ważne jest, aby test jednostkowy nie sprawdzał również jego zależności? Jeśli tak, dlaczego?
Edycja: Rozumiem wartość kpienia z zewnętrznych zależności, takich jak bazy danych, sieci, usługi sieciowe itp. (Podziękowania dla Anny Lear za motywowanie mnie do wyjaśnienia tego). Odniosłem się do wewnętrznych zależności , tj. Innych klas, funkcji statycznych itp. które nie mają żadnych bezpośrednich zależności zewnętrznych.
Odpowiedzi:
To kwestia definicji. Test z zależnościami jest testem integracyjnym, a nie testem jednostkowym. Powinieneś także mieć pakiet testów integracyjnych. Różnica polega na tym, że pakiet testów integracyjnych może być uruchamiany w innym środowisku testowym i prawdopodobnie nie jest częścią kompilacji, ponieważ zajmuje to więcej czasu.
Dla naszego produktu: Nasze testy jednostkowe są przeprowadzane z każdą wersją, co zajmuje kilka sekund. Podzbiór naszych testów integracyjnych jest uruchamiany przy każdym odprawie i zajmuje 10 minut. Nasz pełny pakiet integracyjny jest uruchamiany każdej nocy, zajmuje 4 godziny.
źródło
Testowanie ze wszystkimi istniejącymi zależnościami jest nadal ważne, ale bardziej dotyczy sfery testów integracyjnych, jak powiedział Jeffrey Faust.
Jednym z najważniejszych aspektów testów jednostkowych jest zapewnienie wiarygodności testów. Jeśli nie ufasz, że pozytywny test naprawdę oznacza, że wszystko jest w porządku, a test zakończony niepowodzeniem naprawdę oznacza problem w kodzie produkcyjnym, twoje testy nie są tak przydatne, jak mogą być.
Aby twoje testy były wiarygodne, musisz zrobić kilka rzeczy, ale skupię się tylko na jednej z tych odpowiedzi. Musisz upewnić się, że są łatwe do uruchomienia, aby wszyscy programiści mogli je łatwo uruchomić przed wpisaniem kodu. „Łatwy do uruchomienia” oznacza, że twoje testy działają szybko i nie jest wymagana żadna rozbudowana konfiguracja ani konfiguracja, aby je uruchomić. Idealnie byłoby, gdyby każdy mógł sprawdzić najnowszą wersję kodu, uruchomić testy od razu i zobaczyć, jak się sprawdzają.
Pozbycie się zależności od innych rzeczy (system plików, baza danych, usługi sieciowe itp.) Pozwala uniknąć konieczności konfigurowania i sprawia, że ty i inni programiści mniej podatni na sytuacje, w których kusi cię, by powiedzieć: „Och, testy nie powiodły się, ponieważ ja nie” mieć skonfigurowany udział sieciowy. No cóż. Uruchomię je później. ”
Jeśli chcesz przetestować to, co robisz z niektórymi danymi, testy jednostkowe tego kodu logiki biznesowej nie powinny obchodzić, w jaki sposób otrzymujesz te dane. Możliwość przetestowania podstawowej logiki aplikacji bez polegania na wsparciu takich rzeczy jak bazy danych jest niesamowita. Jeśli tego nie robisz, tracisz.
PS Powinienem dodać, że zdecydowanie można nadpisać inżynierię w imię testowalności. Testowanie aplikacji pomaga złagodzić ten problem. Ale w każdym razie słabe implementacje podejścia nie powodują, że podejście jest mniej ważne. Wszystko może być niewłaściwie wykorzystane i przesadzone, jeśli nie będzie się pytać „dlaczego to robię?” podczas rozwoju.
Jeśli chodzi o wewnętrzne zależności, sprawy stają się trochę mętne. Lubię o tym myśleć, że chcę chronić moją klasę w jak największym stopniu przed zmianą z niewłaściwych powodów. Jeśli mam konfigurację taką jak ta ...
Generalnie nie dbam o
SomeClass
to, jak powstaje. Chcę tylko tego użyć. Jeśli SomeClass zmieni się i teraz wymaga parametrów do konstruktora ... to nie jest mój problem. Nie powinienem zmieniać MyClass, aby to uwzględnić.Teraz dotyczy to tylko części projektowej. Jeśli chodzi o testy jednostkowe, chcę również chronić się przed innymi klasami. Jeśli testuję MyClass, lubię wiedzieć, że nie ma żadnych zewnętrznych zależności, że SomeClass w pewnym momencie nie wprowadził połączenia z bazą danych lub innego zewnętrznego łącza.
Ale jeszcze większym problemem jest to, że wiem również, że niektóre wyniki moich metod opierają się na danych wyjściowych z niektórych metod na SomeClass. Bez wyśmiewania / eliminowania SomeClass, mógłbym nie mieć możliwości różnicowania zapotrzebowania na dane wejściowe. Jeśli mi się poszczęści, być może uda mi się skomponować moje środowisko w teście w taki sposób, że wyzwoli właściwą odpowiedź z SomeClass, ale pójście w ten sposób wprowadza złożoność do moich testów i czyni je kruchymi.
Przepisanie MyClass, aby zaakceptowało wystąpienie SomeClass w konstruktorze, pozwala mi utworzyć fałszywe wystąpienie SomeClass, które zwraca żądaną wartość (za pośrednictwem frameworku lub ręcznie). W tym przypadku generalnie nie muszę wprowadzać interfejsu. To, czy to zrobić, czy nie, jest pod wieloma względami osobistym wyborem, który może być podyktowany twoim wybranym językiem (np. Interfejsy są bardziej prawdopodobne w C #, ale na pewno nie potrzebujesz takiego w Ruby).
źródło
Oprócz problemu z testem integracji z jednostką należy wziąć pod uwagę następujące kwestie.
Class Widget ma zależności od klas Thingamajig i WhatsIt.
Test jednostkowy widżetu kończy się niepowodzeniem.
W której klasie leży problem?
Jeśli odpowiedziałeś „odpal debugger” lub „przeczytaj kod, dopóki go nie znajdę”, rozumiesz znaczenie testowania tylko jednostki, a nie zależności.
źródło
Wyobraź sobie, że programowanie jest jak gotowanie. Następnie testowanie jednostkowe jest takie samo, jak upewnianie się, że składniki są świeże, smaczne itp. Natomiast testy integracyjne to upewnianie się, że posiłek jest smaczny.
W ostatecznym rozrachunku upewnienie się, że posiłek jest pyszny (lub system działa) jest najważniejszą rzeczą, ostatecznym celem. Ale jeśli twoje składniki, to znaczy jednostki, działają, będziesz miał tańszy sposób, aby się dowiedzieć.
Rzeczywiście, jeśli możesz zagwarantować, że twoje jednostki / metody działają, istnieje większe prawdopodobieństwo, że masz działający system. Podkreślam „bardziej prawdopodobne”, niż „pewne”. Nadal potrzebujesz testów integracyjnych, tak jak nadal potrzebujesz kogoś, kto spróbuje ugotowanego posiłku i powie, że produkt końcowy jest dobry. Łatwiej będzie ci się tam dostać ze świeżymi składnikami, to wszystko.
źródło
Testowanie jednostek poprzez ich całkowitą izolację pozwoli na przetestowanie WSZYSTKICH możliwych odmian danych i sytuacji, w których jednostka ta może zostać przesłana. Ponieważ jest odizolowany od reszty, możesz zignorować wariacje, które nie mają bezpośredniego wpływu na testowane urządzenie. to z kolei znacznie zmniejszy złożoność testów.
Testowanie przy różnych poziomach zintegrowanych zależności pozwoli przetestować określone scenariusze, które mogły nie zostać przetestowane w testach jednostkowych.
Oba są ważne. Po przeprowadzeniu testu jednostkowego niezmiennie będziesz występować bardziej złożony subtelny błąd występujący podczas integracji komponentów. Sam test integracji oznacza, że testujesz system bez pewności, że poszczególne części maszyny nie zostały przetestowane. Myślenie, że można uzyskać lepszy pełny test po prostu przez wykonanie testów integracyjnych, jest prawie niemożliwe, ponieważ im więcej dodanych składników, liczba kombinacji wejściowych rośnie BARDZO szybko (myśl silnie), a utworzenie testu o wystarczającym zasięgu staje się bardzo szybko niemożliwe.
Krótko mówiąc, trzy poziomy „testów jednostkowych”, których zwykle używam w prawie wszystkich moich projektach:
Wstrzykiwanie zależności jest jednym z bardzo skutecznych sposobów na osiągnięcie tego, ponieważ pozwala bardzo łatwo izolować komponenty do testów jednostkowych bez zwiększania złożoności systemu. Twój scenariusz biznesowy może nie uzasadniać zastosowania mechanizmu wstrzykiwania, ale twój scenariusz testowy prawie tego wymaga. To dla mnie wystarcza, aby uczynić go niezbędnym. Możesz ich również użyć do niezależnego testowania różnych poziomów abstrakcji poprzez częściowe testy integracji.
źródło
Jednostka. Oznacza liczbę pojedynczą.
Testowanie 2 rzeczy oznacza, że masz dwie rzeczy i wszystkie zależności funkcjonalne.
Jeśli dodasz trzecią rzecz, zwiększysz zależności funkcjonalne w testach poza liniowo. Wzajemne powiązania między rzeczami rosną szybciej niż liczba rzeczy.
Istnieje n (n-1) / 2 potencjalnych zależności między n badanych pozycji.
To jest główny powód.
Prostota ma wartość.
źródło
Pamiętasz, jak po raz pierwszy nauczyłeś się robić rekurencję? Mój prof powiedział „Załóżmy, że masz metodę, która robi x” (np. Rozwiązuje Fibbonacciego dla dowolnego x). „Aby rozwiązać x, musisz wywołać tę metodę dla x-1 i x-2”. Z tych samych względów eliminacja zależności pozwala udawać, że istnieją i sprawdzać, czy bieżąca jednostka robi to, co powinna. Założeniem jest oczywiście, że testujesz zależności tak rygorystycznie.
Jest to w istocie SRP w pracy. Skupienie się na pojedynczej odpowiedzialności nawet za twoje testy, eliminuje ilość żonglerki umysłowej, którą musisz zrobić.
źródło
(To drobna odpowiedź. Dzięki @TimWilliscroft za podpowiedź.)
Usterki można łatwiej zlokalizować, jeśli:
Działa to dobrze na papierze. Jednak, jak pokazano w opisie OP (zależności są błędne), jeśli zależności nie są testowane, trudno byłoby wskazać lokalizację błędu.
źródło
Wiele dobrych odpowiedzi. Dodałbym także kilka innych punktów:
Testowanie jednostkowe pozwala również przetestować kod, gdy nie istnieją zależności . Na przykład ty lub twój zespół nie napisałeś jeszcze innych warstw, a może czekasz na interfejs dostarczony przez inną firmę.
Testy jednostkowe oznaczają również, że nie musisz mieć pełnego środowiska na swoim urządzeniu programistycznym (np. Baza danych, serwer WWW itp.). Chciałbym polecić silny, że wszyscy deweloperzy zrobić mieć takiego środowiska, jednak ściąć to jest, w celu repro błędów itd. Jednakże, jeśli z jakiegoś powodu nie jest możliwe, aby naśladować środowisko produkcyjne następnie testowanie jednostkowe przynajmniej daje pewien poziom Yu zaufania do twojego kodu, zanim trafi on do większego systemu testowego.
źródło
O aspektach projektowych: Wierzę, że nawet małe projekty korzystają z możliwości testowania kodu. Niekoniecznie musisz wprowadzać coś takiego jak Guice (często wystarczy zwykła klasa fabryczna), ale oddzielenie procesu budowy od logiki programowania daje
źródło
Uh ... tutaj są dobre punkty na testach jednostkowych i integracyjnych w tych odpowiedziach!
Brakuje mi tutaj poglądów związanych z kosztami i praktycznych . To powiedziawszy, widzę wyraźnie korzyści z bardzo izolowanych / jednostkowych testów atomowych (być może wysoce niezależnych od siebie i z możliwością uruchamiania ich równolegle i bez żadnych zależności, takich jak bazy danych, system plików itp.) I (wyższego poziomu) testów integracyjnych, ale ... to także kwestia kosztów (czasu, pieniędzy, ...) i ryzyka .
Są więc inne czynniki, które są znacznie ważniejsze (jak „co przetestować” ), zanim pomyślisz o „jak przetestować” z mojego doświadczenia ...
Czy mój klient (domyślnie) płaci za dodatkową ilość testów i testów? Czy podejście bardziej oparte na testach (najpierw pisz testy przed napisaniem kodu) jest naprawdę opłacalne w moim środowisku (analiza ryzyka / kosztów awarii kodu, ludzie, specyfikacje projektu, konfiguracja środowiska testowego)? Kod jest zawsze błędny, ale czy przeniesienie testowania do wykorzystania produkcyjnego może być bardziej opłacalne (w najgorszym przypadku!)?
Zależy to również w dużej mierze od jakości kodu (standardów), ram, IDE, zasad projektowania itp., Których przestrzegasz Ty i Twój zespół, a także od ich doświadczenia. Dobrze napisany, łatwo zrozumiały, wystarczająco dobrze udokumentowany (najlepiej samodokumentujący), modułowy ... kod wprowadza prawdopodobnie mniej błędów niż na odwrót. Tak więc rzeczywista „potrzeba”, presja lub ogólne koszty / ryzyko związane z naprawą / błędami w przypadku obszernych testów mogą nie być wysokie.
Przejdźmy do skrajności, gdzie sugerował współpracownik z mojego zespołu, musimy spróbować przetestować jednostkowo nasz kod warstwy modelu czystego Java EE z pożądanym 100% pokryciem dla wszystkich klas wewnątrz i wyśmiewaniem bazy danych. Lub menedżer, który chciałby, aby testy integracyjne obejmowały 100% wszystkich możliwych przypadków użycia w świecie rzeczywistym i przepływów pracy w interfejsie sieciowym, ponieważ nie chcemy ryzykować niepowodzenia żadnego przypadku użycia. Ale mamy napięty budżet w wysokości około 1 miliona euro, dość napięty plan kodowania wszystkiego. Środowisko klienta, w którym potencjalne błędy aplikacji nie byłyby bardzo dużym zagrożeniem dla ludzi lub firm. Nasza aplikacja zostanie przetestowana wewnętrznie z (niektórymi) ważnymi testami jednostkowymi, testami integracji, testami kluczowych klientów z zaprojektowanymi planami testów, fazą testów itp. Nie tworzymy aplikacji dla niektórych fabryk jądrowych lub produkcji farmaceutycznej!
Sam próbuję napisać test, jeśli to możliwe, i rozwinąć się podczas testowania mojego kodu. Ale często robię to od góry do dołu (testy integracyjne) i staram się znaleźć dobry punkt, w którym „wyciąć warstwę aplikacji” do ważnych testów (często w warstwie modelu). (ponieważ często chodzi o „warstwy”)
Ponadto kod testu jednostkowego i integracyjnego nie przychodzi bez negatywnego wpływu na czas, pieniądze, konserwację, kodowanie itp. Jest fajny i należy go stosować, ale należy zachować ostrożność i rozważyć konsekwencje przy rozpoczynaniu lub po 5 latach wielu opracowanych testów kod.
Powiedziałbym więc, że tak naprawdę zależy to od zaufania i oceny kosztów / ryzyka / korzyści ... jak w prawdziwym życiu, w którym nie możesz i nie chcesz biegać z wieloma lub 100% mechanizmami bezpieczeństwa.
Dziecko może i powinno gdzieś wspiąć się na górę, może spaść i zranić się. Samochód może przestać działać, ponieważ wlałem niewłaściwe paliwo (nieprawidłowe wejście :)). Tost może zostać spalony, jeśli przycisk czasu przestanie działać po 3 latach. Ale ja nigdy, nie chcę jechać autostradą i trzymać w rękach moją odłączoną kierownicę :)
źródło