fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
7 minut

[UT-1] Co to są testy i po co są testy jednostkowe?


11.08.2011

[ten post jest częścią mojego minicyklu o testach, pełna lista postów: tutaj]

Test jednostkowy to nic innego jak kod wykonujący inny kod w kontrolowanych warunkach. Jego zadaniem jest weryfikacja (bez ingerencji programisty), że testowany kod działa poprawnie. Robi to w sposób dość banalny: autor testu dostarcza dane wejściowe (input), test wykonuje pewne instrukcje i sprawdza, czy rezultat działań (output) zgodny jest z oczekiwaniami.

W świecie idealnym każdy test bada jedną ścieżkę wykonania jednej metody.

Czy jednak jedyną rolą testów jednostkowych jest sprawdzenie działania poprawności napisanego kodu? Otóż… nie

Design aplikacji

Moim zdaniem główną rolą testów jednostkowych jest poprawienie designu tworzonej aplikacji. Nie chodzi oczywiście o jej wygląd (UI), a o architekturę. Każdy testowany komponent, aby pokrycie go testami jednostkowymi było możliwe, musi udostępniać pewne API. API to jest wykorzystywane zarówno przez testy, jak i przez inne komponenty składające się na całość systemu.

Stosowanie testów jednostkowych (szczególnie przy kierowaniu się wytycznymi metodyki TDD – test driven development) wymusza na programiście chwilę refleksji… Zamiast bezmyślnie rzucać się w wir kodowania, co bardzo często ma miejsce (wiem z doświadczenia…), twórca kodu najpierw oddaje się “krótkiej zadumie”:) nad sposobem implementacji danej funkcjonalności. Jak najlepiej to zrobić, żeby nie tylko działało, ale jeszcze było testowalne? Jak rozplanować odpowiedzialności pomiędzy klasy? Gdzie dodać interfejs? Jakich zależności potrzebuje? Jak całość wpasuje się w już istniejący kod? I, co bardzo ważne, co tak naprawdę musi napisać żeby spełnić wymagania nie marnując czasu na rzeczy zbędne?

Taka chwila zastanowienia nad implementowaną funkcjonalnością pozwala zrozumieć przyświecającą jej ideę, dzięki czemu dobrany sposób realizacji ma szansę w największym stopniu odpowiadać faktycznemu zapotrzebowaniu na kod.

Dodatkowo stosowanie testów jednostkowych niejako implikuje wykorzystanie zalecanych praktyk programistycznych. U mnie akurat zainteresowanie testami jednostkowymi rozpoczęło całkiem nowy rozdział w dev-przygodzie. Wtedy zrozumiałem praktyczną wartość Inversion of Control/Dependency Injection. Wtedy dotarło do mnie, po kiego grzyba stosować programowanie pod interfejs/kontrakt. Wtedy właśnie zrozumiałem, że klepanie kolejnych linii “działającego” kodu ma dość krótkie nogi i można to zrobić najzwyczajniej w świecie – lepiej.

Tym samym dochodzimy do teoretycznie oczywistego wniosku: trudny do przetestowania kod można napisać lepiej. Jasne, KAŻDY bez wyjątku kod można napisać lepiej, ale trudność testowania może być wskaźnikiem obszarów żebrzących o refactoring.

Weryfikacja działania bez uruchamiania

Sprawdzanie co chwila, czy napisany kod działa, poprzez uruchomienie aplikacji/stronki, przeklikanie się przez interfejs i oczekiwanie na rezultat to straszna strata czasu (dodatkowo jest to niemoralne:)). Dobre testy jednostkowe mogą tutaj bardzo pomóc. Mając testy pokrywające wymaganą funkcjonalność oraz kod spełniający w teorii te założenia, nie musimy uruchamiać całego systemu. Wystarczy włączyć testy i poczekać kilka sekund na ich wykonanie. Zielony pasek w test runnerze oznacza jedną z dwóch rzeczy: albo nasz kod działa jak powinien (hurra!) albo mamy źle napisane testy. Innej możliwości nie ma. A z czasem, gdy wraz z praktyką pisane testy będą miały coraz większą wartość, współczynnik zaufania do nich będzie rósł. W końcu wystarczy jedno uruchomienie “na wszelki wypadek”, po zakończeniu całej funkcjonalności, i weryfikacja zaimplementowanych zadań.

Testy jako dokumentacja

Tworzenie dokumentacji “pisanej” do projektu jest zwykle (moim nieskromnym zdaniem – prawie zawsze) jedynie stratą czasu (chodzi oczywiście o dokumentację wytworzonego kodu, nie funkcjonalną). Powód tego jest bardzo prosty: taka dokumentacja nadaje się do czegokolwiek tylko w momencie jej pisania. Po kilku dniach/tygodniach/miesiącach ma się nijak do opisywanego kodu. Bo czy ktoś faktycznie dba o jej synchronizację? Jeśli tak – to szczerze temu komuś współczuję.

Często mówi się, że najlepszą dokumentacją kodu jest… sam kod. I jest to prawda. Testy jednostkowe mogą być traktowane właśnie w ten sposób. Jest to kod – więc są czytelne dla programisty. Są regularnie uruchamiane – a więc siłą rzeczy są z testowanym kodem zsynchronizowane. Dobra struktura testów pozwala na zorientowanie się nie tylko w architekturze i działaniu systemu, ale także intencjach autora – i co najlepsze, intencje te można zweryfikować poprzez odpalenie testów.

Dodatkowo testy jednostkowe ewoluują wraz z kodem aplikacji, więc z czasem tworzą coraz dokładniejszą i coraz lepszą “narrację” dla produkcyjnych instrukcji.

A w przypadku sytuacji, które nie są do końca zrozumiałe przez spojrzenie w sam czytany kod, testy mogą stanowić uzupełnienie uzasadniające takich a nie innych decyzji podjętych na etapie programowania. Czyli – mogą w dużej mierze zastąpić komentarze w kodzie.

Testy jako zabezpieczenie przed regression bugs

Ten punkt powinien tak naprawdę być nieco wyżej… ale nie jest. Z mojego doświadczenia niestety wynika, że zbytnie ufanie w “siatkę bezpieczeństwa” rozpiętą przez testy podczas zmian w oprogramowaniu nie kończy się dobrze. Początkowo podchodziłem w takim przypadku do testów z wielkim zaufaniem i traktowałem je jako zapewnienie, że po zmianach kod nadal działa jak powinien – bo “pasek test runnera zaświecił się na zielono”. Miało to konsekwencje dość przykre…

Ale wina leży oczywiście wyłącznie po mojej stronie. Prawda jest taka, że świetne testy chronią przed bugami przy zmianach w kodzie. Ale nauka pisania świetnych testów trwa latami. Gdy już jednak nauczymy się pisać testy tam gdzie trzeba, tak jak trzeba, zmiany w istniejącym kodzie będą o wiele przyjemniejsze i prostsze. Gdy popełnimy błąd – testy to wychwycą. Muszą jednak spełniać ten najważniejszy warunek – muszą być dobre (cokolwiek to w tym momencie znaczy). W przeciwnym wypadku doświadczenia będą zgoła odmienne. Złe/niepotrzebne testy utrudniają zmiany.

Testy jako narzędzie do nauki

Przyznaję, że sam nie wpadłem na takie zastosowanie testów jednostkowych. Co prawda często zdarza mi się napisać jakiś teścik żeby sprawdzić ten czy inny aspekt działania flaków .NETa lub zewnętrznej biblioteki, ale Szymon Pobiega poszedł kilka kroków dalej. Wziął NHibernate, wziął dokumentację do NH i stworzył projekt NHibernate Deep Dive, gdzie testami jednostkowymi demonstruje działanie różnych aspektów tego ORMa. Co prawda pisałem już o tym po starcie projektu, ale w poście omawiającym rolę testów nie sposób to pominąć.


To na pierwszy odcinek tyle. Czy się podoba?

A może coś nakręciłem? Coś pomieszałem? Coś pominąłem? Czekam na uwagi i krytykę.

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Notify of
Artur
Artur

1) "trudność testowania może być wskaźnikiem obszarów żebrzących o refactoring" – imho, najważniejsze zdanie w tym poście
2) testy powinny być odpalane automatycznie (CI), po programiści robią to (za) rzadko
3) fajnie, że poruszyłeś sprawę, że testy nie dają nam gwarancji bezbłędnej aplikacji. Nieraz zdarzyło mi się zrobić błąd w testach dotyczących obliczeń i ten sam błąd w implementacji. UT nie jest lekiem na całe zło świata, a czasami takie podejście jest prezentowane w necie.
4) jeżeli nie mamy testów, to strach poprawić błąd w kodzie, bo poprawiając go możemy spowodować 3 inne i nie dowiemy się o tym

Galaktyczny_Joe
Galaktyczny_Joe

"Każdy testowany komponent, aby pokrycie go testami jednostkowymi było możliwe, musi udostępniać pewne API. API to jest wykorzystywane zarówno przez testy, jak i przez inne komponenty składające się na całość systemu." Światowej sławy Scott Meyers w ksiażce "C++ 50 efektywnych sposobów na udoskonaleni Twoich programów" opisał, że należy starać się dążyć do kompletnych i minimalnych interfejsów klasy. Ty natomiast piszesz, że trzeba pisać kod z myślą o testach, czyli na ruszt rzucamy więcej publicznych metod niż tego oczekujemy, czy to jest rozsądne? W książce Piękny Kod, pada hasło, aby każda tworzona klasa brała na barki tylko jedno zadanie. Co oczywiście… Read more »

dooh
dooh

[quote] Ty natomiast piszesz, że trzeba pisać kod z myślą o testach, czyli na ruszt rzucamy więcej publicznych metod niż tego oczekujemy, czy to jest rozsądne? [/quote] A gdzie procent napisał o "rzucaniu na ruszt publicznych metod"? Myślę że nie miał tego na myśli. IMHO testowanie każdego najmniejszego trybiku w machinie nie ma sensu (mam na myśli testowanie prywatnych metod z pomocą refleksji, publicznych metod które zostały zrobione takimi tylko po to aby być poddane UT). Taka polityka potęguje liczbę modyfikacji kodu jakie trzeba wykonać w chwili zmiany wymagań (więcej testów idzie do piachu/musi być zmieniona) [quote] To wydaje mi… Read more »

Maks

Unit Testy przynajmniej z mojego doświadczenia sprawdzały się w projektach o średniej wielkości z nieduża liczbą zależności od zewnętrznych systemów, API, serwisów. Oczywiście utesty mogą być dokumentacją, aczkowlwiek jeżeli testowany kod intesywnie korzysta z Dependency Injection to nasze testy będą prawdopodobnie wyglądały jak "ciemna masa", którą tylko wybrańcy będą w stanie ogarnąć.

twk
twk

Co do testów jako dokumentacji, polecam zaznajomienie się z Behavior Driven Developmend (BDD), gdzie definicji testów dokonuje się w sposób opisowy pozwalający na wykonanie dokumentacji testowej (a może i funkcjonalnej.
http://specflow.org/specflow/workflow.aspx

uolot

Bardzo się cieszę, że rozpocząłeś cykl o TDD, tym bardziej, że z tego co piszesz jesteś praktykiem, a nie tylko teoretykiem (jak ja :)).

Wprawdzie technologie dotnetowe, to może jakieś 5% tego co robię, ale przyznam, że poruszasz na tyle ciekawe tematy (i do tego w przystępny sposób), że nawet osoba nie związana z produktami Microsoftu może wyciągnąć dużo użytecznej wiedzy z Twojego bloga – tego samego zresztą spodziewam się po serii o testach jednostkowych. Tak trzymać! :)

PiotrB

@Galaktyczny_Joe: Masz chyba na myśli "Clean Code", więc przyjmij do wiadomości, że to co procent napisał, jest z nią zgodne.

Co do całego cyklu, w końcu ktoś wystarczająco kompetentny opisze temat po polsku (oczywiście IMHO).

Marcin
Marcin

Unit Testy przynajmniej z mojego doświadczenia sprawdzały się w projektach o średniej wielkości z nieduża liczbą zależności od zewnętrznych systemów, API, serwisów. Oczywiście testy mogą być dokumentacją, aczkolwiek jeżeli testowany kod intensywne korzysta z Dependency Injection to nasze testy będą prawdopodobnie wyglądały jak "ciemna masa", którą tylko wybrańcy będą w stanie ogarnąć. Nie zgodzę się z tobą ponieważ mam zupełnie inne doświadczenia. Pisząc mała aplikację z wiedzą, że nie będzie rozwijana można lać na testy. Szczególnie jeśli samemu się nad nią pracuje. W rzeczywistości im większy system tym więcej testów wymaga i tym są one cenniejsze. Dodatkowo nie można poprzestać… Read more »

procent

Artur,
Co do CI – pełna zgoda. O tym będzie w którymś z następnych odcinków (paragraf w stylu "jak/kiedy uruchamiać testy")

procent

Galaktyczny_Joe, Nie zamierzam się spierać z żadną światową sławą. Ale nie widzę sprzeczności między tym co napisałem, a tym co przytoczyłeś. Jeśli nie masz minimalnego interfejsu klasy to klasę jest ciężko przetestować. Jeśli masz funkcjonalność pochowaną w prywatnych helperach – to klasę jest ciężko przetestować. Jeśli masz klasę odpowiedzialną za milion rzeczy – to ciężko ją przetestować. A co to oznacza – napisałem w poście: kod trzeba zmienić. Czy te helpery nie powinny być wywalone do osobnej klasy? Czy klasy nie wypada podzielić na kilka mniejszych? Hasło do wyguglowania: high cohesion, low coupling. Co do testowania klas UI – nie… Read more »

procent

Maks,
Testy sprawdzają się w każdym systemie – i małym i wielkim. A Dependency Injection (a właściwie Inversion of Control, której to reguły DI jest jedynie implementacją) to Twój (i testów) przyjaciel. Co do "testów wyglądających jak ciemna masa" – powtórzę się z poprzedniego komentarza: pisanie testów to trudna sztuka. Mam nadzieję, że tym minicyklem skłonię kogoś do poświęcenia jej czasu. Oraz oczywiście że sam się czegoś nauczę, bo zgłębiam temat od lat, a mimo to nie napiszę o sobie "spec". Raczej "ten, który ma za sobą bardzo wiele beznadziejnie napisanych, bezużytecznych testów".

procent

twk,
Dzięki za podlinkowanie, o BDD będzie osobny odcinek (w spis treści nr 7)

procent

uolot,
Naprostuję: to ma być w zamierzeniu cykl o testach *automatycznych* ogólnie, a nie o ściśle zdefiniowanych jednostkowych. W szczególności nie o TDD.
A za komplementy dziękuję, staram się jak mogę:). Mam nadzieję, że faktycznie coś fajnego z tego cyklu wyniknie.

procent

PiotrB,
Powiem tak: na moje doświadczenia składają się bardziej porażki niż sukcesy w podejściach do tworzenia testów. O ile taka wiedza jest moim zdaniem bardziej cenna niż klepanie suchych formułek "jakie to testy są zajebiste" bez odniesienia do rzeczywistości i praktyki, to jednak określenie "ktoś wystarczająco kompetentny" może być mylne. Ciągle się uczę, ciągle szukam – i to co piszę równie dobrze może wcale nie mieć wielkiej wartości. Ale zobaczymy, tak czy siak – pouczmy się wszyscy wspólnie:).

Maks

@Procent: Może źle się wyraziłem ze słowem "sprawdzają", ale po prostu poprzeczka trudności implementacji unit testów idzie znacząco w góre wraz ze wzrostem skomplikowania systemu.

@Marcin: Zgoda, aczkolwiek małe aplikacje często ewoluują w systemy utrzymywane przez lata, zatem inwestycja w UTesty może być opłacalna nawet przy niedużych projektach (szczególnie tych "perspektywicznych").

Z tego co wiem DI można i przez konstruktor i prze "property", i chyba nie ma "jedynej słusznej" szkoły.

igby
igby

Z tego co widzę szykuje się niezła "walka"! Mam nadzieje, że będzie owocna;)

@Maks: Masz rację. Ostatnio po pół roku pracy mój mały projekcik z lekka przy tył i UT, jak na razie, ratują mnie w dużej części (w pozostałej są źle napisane albo zawierają błędy:D)

A tak od siebie: ja swoją przygodę na całego zacząłem od książki "Art of Unit Testing", mogę ją polecić szczerze polecić(lekka i bez zbędnych opowiastek).

reVis

Dobrze będzie poczytać o UT od strony bardziej doświadczonego kolegi. Sam ostatnio zrozumiałem jak łatwo zapędzić się z testami w ślepy róg. W miejsce gdzie testy zaczynają być udręką, którą ciągnie się za resztą kodu. Ale wyciągniętego doświadczenia nikt mi nie zabierze ;)

PiotrB

@procent: Miałem na myśli raczej ogół kompetencji, realną praktykę oraz umiejętność zainteresowania tekstem. Przynajmniej z tego, co się lansujesz na blogu ;).
A podejścia do testów to faktycznie zbieranie doświadczeń przez robienie baboli i wchodzenie w ślepe uliczki, więc doskonale to rozumiem ("U nas" się już nie mówi: ‘zrobiłeś błąd’ ani nawet ‘posadziłeś buga’, lepiej brzmi ‘nabrałeś doświadczenia’ i jest to święta prawda).

Jazonet
Jazonet

Będąc na początku zawodowej dev-przygody, podchodzę do UT raczej dość beztrosko(czyt. albo ich nie piszę, albo piszę zaledwie kilka najważniejszych). Myślę że seria wpisów na Twoim blogu, zmusi/zachęci nieświadomych do zastanowienia się ( może nawet sposobu pracy ) nad testami jednostkowymi. Czekam z niecierpliwością na kolejne wpisy.

Moja książka

Facebook

Zobacz również