Testowanie oprogramowania

SonarQube, Sonar, Statyczna analiza kodu

SonarQube to narzędzie do automatycznej analizy jakości kodu, które wykrywa błędy, luki bezpieczeństwa i naruszenia standardów programowania. Umożliwia ciągłe monitorowanie projektu i szybkie reagowanie na potencjalne problemy. Dzięki integracji z narzędziami CI/CD i intuicyjnemu interfejsowi, wspiera zespoły programistyczne w utrzymaniu wysokiej jakości kodu.

Analiza statyczna kodu – co to jest i dlaczego warto?

Analiza statyczna kodu to proces sprawdzania kodu źródłowego bez uruchamiania programu. Innymi słowy, narzędzie analizuje tekst naszego kodu, aby wychwycić błędy, potencjalne bugi czy naruszenia dobrych praktyk. W przeciwieństwie do analizy dynamicznej (która odbywa się podczas działania aplikacji, np. poprzez testy), analiza statyczna skupia się na strukturze i składni kodu. Dzięki temu możemy wykryć problemy już na etapie pisania kodu, zanim jeszcze uruchomimy program czy przekażemy go do testów.

Koszt błędu

Koszt błędu
(źródło: researchgate.net)

Dlaczego to ważne? Każdemu programiście – zwłaszcza początkującemu – zdarza się popełnić błędy w kodzie. Część z nich wychwycimy dopiero podczas działania programu (np. gdy aplikacja nagle się wyłoży), ale wiele pomyłek można znaleźć znacznie wcześniej. Tutaj z pomocą przychodzi statyczna analiza kodu. To trochę jak automatyczny korektor lub „spell-check” dla programisty: ostrzeże Cię, że coś może być nie tak, zanim jeszcze problem się zmaterializuje. W efekcie oszczędzasz czas (bug znaleziony przed testami to mniej poprawek na ostatnią chwilę) i poprawiasz jakość swojego kodu.

Zalety analizy statycznej kodu

Jakie konkretnie korzyści daje nam statyczna analiza kodu?

Oto najważniejsze z punktu widzenia początkującego programisty:

  • Wczesne wykrywanie błędów: Narzędzia do analizy statycznej znajdują wiele pomyłek na bardzo wczesnym etapie. Zamiast głowić się, czemu program dziwnie się zachowuje, dostajesz od razu podpowiedź: „Uwaga, tu może wystąpić błąd!” Dzięki temu naprawisz go zanim kod trafi do dalszych testów czy na produkcję.
  • Poprawa jakości i czytelności kodu: Analiza statyczna zwraca uwagę nie tylko na błędy, ale też na tzw. code smell – czyli fragmenty kodu, które co prawda działają, ale sugerują złą praktykę lub mogą sprawiać kłopoty w przyszłości. Eliminując takie miejsca, twój kod staje się czystszy, bardziej czytelny i łatwiejszy w utrzymaniu.
  • Standaryzacja i dobre praktyki: Większość narzędzi posiada zestaw reguł opartych o sprawdzone standardy kodowania. Przykładowo, mogą wymuszać odpowiednie nazewnictwo zmiennych, konwencje formatowania czy struktury. To pomaga wyrobić dobre nawyki – szczególnie ważne, gdy dopiero uczysz się profesjonalnego podejścia do pisania kodu.
  • Oszczędność czasu w code review: Jeśli w projekcie praktykujecie code review (wzajemne przeglądy kodu), analiza statyczna automatycznie wyłapie część oczywistych uchybień (np. brakujące final przy zmiennych, nieużywane importy itp.). Dzięki temu podczas manualnego code review współpracownicy mogą skupić się na istotniejszych aspektach logiki i architektury, zamiast wytykać drobnostki.
  • Poprawa bezpieczeństwa: Zaawansowane narzędzia (takie jak SonarQube) potrafią wykrywać także podatności bezpieczeństwa. Ostrzegą Cię np. przed użyciem niebezpiecznej funkcji, ryzykiem SQL Injection czy przechowywaniem hasła na sztywno w kodzie. To szczególnie ważne, bo początkujący często nie są świadomi wszystkich zagrożeń – narzędzie podpowie, gdzie kod może być narażony.

Podsumowując, statyczna analiza działa trochę jak doświadczony mentor spoglądający przez ramię na Twój kod. Od razu sugeruje poprawki i uczy lepszych praktyk. To świetny sposób, by uczyć się na własnym kodzie, mając dodatkowe zabezpieczenie przed wprowadzeniem oczywistych błędów.

Przykłady problemów wykrywanych przez statyczną analizę kodu

SonarQube statyczna analiza kodu

Skoro wiemy już, że analiza statyczna szuka błędów i „zapachów” w kodzie, przyjrzyjmy się kilku konkretnym przykładom, co takie narzędzie może wychwycić.

Oto typowe problemy, które początkujący (i nie tylko) programiści często popełniają, a które automatyczna analiza kodu potrafi zidentyfikować:

  • Nieużywane zmienne lub funkcje: Jeśli zadeklarowałeś zmienną, ale nigdy jej nie wykorzystujesz, narzędzie Cię o tym poinformuje. Taki martwy kod tylko zaśmieca program – spokojnie można go usunąć.
  • Nigdy niewykonywany kod: Podobnie, analiza wykryje fragmenty, do których nigdy nie prowadzi żadna ścieżka (np. instrukcja w if, który zawsze jest false). To sygnał, że mamy zbędny lub źle napisany kod.
  • Potencjalne wyjątki w czasie działania: Klasyczny przykład to możliwość wystąpienia NullPointerException w Javie. Jeśli wywołujesz metodę na obiekcie, który może być null i nie sprawdziłeś tego – narzędzie podkreśli, że może dojść do błędu. Inny przykład to dzielenie przez zmienną, której wartość może okazać się zerem – statyczna analiza ostrzeże o takim scenariuszu.
  • Wycieki zasobów: Zapomniałeś zamknąć otwarty plik, strumień lub połączenie z bazą danych? Analiza kodu zauważy, że np. w każdej ścieżce wykonania funkcji nie zamykasz pliku po otwarciu. To ważne, bo takie błędy mogą nie od razu być widoczne, a prowadzą do poważnych problemów (np. zbyt wielu otwartych połączeń).
  • Naruszenie zasad bezpieczeństwa: Przykładowo, umieszczenie hasła lub klucza API na sztywno w kodzie źródłowym zostanie oznaczone jako błąd bezpieczeństwa. Podobnie użycie przestarzałych algorytmów kryptograficznych, brak walidacji danych wejściowych czy ryzykowne operacje (np. wywołanie zewnętrznego polecenia systemowego) mogą być wykryte automatycznie.
  • „Code smells” – problemy z czytelnością i utrzymaniem: Narzędzie poinformuje Cię, jeśli np. funkcja jest zbyt długa, klasa ma zbyt wiele odpowiedzialności, duplikuje się kod w kilku miejscach, albo nazwy zmiennych są mało zrozumiałe. To nie są błędy, które od razu spowodują awarię programu, ale sygnały, że warto zrefaktoryzować kod, by był lepszej jakości.

Jak widać, zakres wykrywanych problemów jest szeroki – od drobnych niedociągnięć, przez poważne bugi, aż po kwestie stylu i dobrych praktyk. Dobre narzędzie do analizy statycznej pełni rolę wszechstronnego strażnika jakości, pilnując zarówno poprawności działania, jak i czytelności czy bezpieczeństwa naszego kodu.

SonarQube – narzędzie do analizy statycznej kodu

Skoro mówimy o statycznej analizie, nie sposób nie wspomnieć o SonarQube – jednym z najpopularniejszych narzędzi w tej dziedzinie. SonarQube to rozbudowana platforma od firmy SonarSource, która automatycznie analizuje kod źródłowy i generuje szczegółowe raporty na temat jego jakości. Co ważne, obsługuje wiele języków programowania (Java, Python, JavaScript, C# i dziesiątki innych), więc jest przydatna w różnych projektach.

Co robi SonarQube? Po przeanalizowaniu projektu SonarQube przedstawi Ci listę wykrytych błędów (bugs), podatności bezpieczeństwa (vulnerabilities) oraz „code smells”. Każde takie znalezisko jest opisane – dostajesz informację, dlaczego dana konstrukcja jest problematyczna oraz często propozycję, jak to naprawić.

Przykładowo, SonarQube może zgłosić:

  • „Ta metoda ma zbyt wysoką złożoność – rozważ podzielenie jej na mniejsze części”
  • albo „Potencjalny błąd: zmienna x może być null w tym miejscu”.

Dzięki temu nie tylko wiesz co poprawić, ale też dlaczego – co ma wartość edukacyjną dla mniej doświadczonych programistów.

SonarQube działa najczęściej jako serwer z graficznym interfejsem dostępnym przez przeglądarkę. Integruje się z procesem wytwarzania oprogramowania – np. można go podłączyć do systemu CI/CD.

Typowy scenariusz w projekcie wygląda tak, że gdy programista wypycha kod (np. robi git push na repozytorium), uruchamia się analiza SonarQube na serwerze. Jeśli kod nie przejdzie pewnych ustalonych kryteriów jakości (np. zbyt dużo nowych błędów), to build zostanie oznaczony jako niezaliczony. Taki mechanizm nazywa się Quality Gate – zespół ustala, jakie warunki musi spełniać kod (np. brak błędów krytycznych, mniej niż X code smellów o wysokim priorytecie, odpowiednie pokrycie testami), a SonarQube pilnuje tych ustaleń automatycznie.

Dla Ciebie, jako początkującego programisty, ważne jest, że SonarQube pozwala śledzić jakość kodu w czasie. Możesz na bieżąco obserwować na dashboardzie, jak Wasz projekt się zmienia: ile macie otwartych błędów, ile długiego kodu (tzw. technical debt), jaki jest trend – czy nowe zmiany poprawiają jakość, czy może wprowadzają nowe problemy. SonarQube staje się takim centrum dowodzenia do spraw kodu: zaglądasz tam i od razu widzisz, gdzie są słabe punkty, które pliki wymagają uwagi, a które są w porządku.

Warto dodać, że SonarQube ma różne edycje – Community (darmową) i komercyjne – jednak w kontekście nauki i własnych projektów w zupełności wystarcza ta darmowa, która i tak oferuje ogrom możliwości. W kolejnej sekcji zobaczymy, jak łatwo uruchomić SonarQube na własnym komputerze, żeby móc go samodzielnie wypróbować.

SonarLint – statyczna analiza kodu w Twoim IDE

SonarLint – statyczna analiza kodu

SonarLint – statyczna analiza kodu

SonarQube to potężny serwer do analizy kodu w projekcie, ale co z bieżącym pisaniem kodu? Tutaj do akcji wkracza SonarLint – narzędzie, które działa jak Twój prywatny asystent ds. jakości kodu bezpośrednio w IDE (środowisku programistycznym). SonarLint to wtyczka (plugin) dostępna m.in. dla IntelliJ IDEA, Visual Studio, Eclipse czy VS Code, która na bieżąco sprawdza kod, który piszesz, wykorzystując reguły Sonara.

Intellij IDEA statyczna analiza kodu

Intellij IDEA statyczna analiza kodu

➡ ZOBACZ 👉: IDE Zintegrowane środowisko programistyczne

Możesz wyobrazić sobie SonarLint jako takiego anioła stróża nad Twoim edytorem kodu. Gdy tylko napiszesz linijkę, SonarLint ją analizuje i w razie wykrycia problemu od razu podkreśla go lub zaznacza na marginesie. Przykład: wpisujesz fragment, który może doprowadzić do podzielenia przez zero, albo używasz nieużywanej zmiennej – SonarLint natychmiast Cię o tym poinformuje, zanim nawet uruchomisz aplikację. To trochę jak autokorekta, tyle że dla kodu: nie czekasz na kompilację czy testy, od razu dostajesz feedback.

SonarLint analiza kodu

SonarLint analiza kodu

Dla początkujących to szczególnie cenne, bo uczy na żywym organizmie. Każde podkreślenie to okazja, by zadać sobie pytanie: „Dlaczego SonarLint uważa, że to błąd lub zła praktyka?”. Wtyczka zazwyczaj daje krótkie wyjaśnienie. Na przykład może ostrzec: „Ta pętla jest zbyt złożona – rozważ uproszczenie” albo „Używasz == do porównywania napisów w Javie – to błąd, użyj equals() zamiast tego”. Mając taką podpowiedź, możesz od razu poprawić kod i nauczyć się czegoś nowego.

SonarLint potrafi działać samodzielnie, ale najlepsze efekty daje połączenie go z SonarQube. Jeśli Wasz zespół ma SonarQube z ustalonym zestawem reguł jakości, możesz skonfigurować SonarLint, by łączył się z tym serwerem. Wtedy SonarLint będzie korzystał dokładnie z tych samych reguł i konfiguracji co SonarQube, zapewniając spójność. Innymi słowy, to co SonarQube wykryłby na serwerze po wypchnięciu kodu, SonarLint pokaże Ci już podczas pisania – dzięki temu unikniesz wysyłania kodu, który i tak by nie przeszedł kontroli jakości.

SonarLint reguły analizy kodu

SonarLint reguły analizy kodu

Podsumowując, SonarLint to świetne narzędzie do codziennej pracy. Szczególnie, jeśli dopiero się uczysz, warto je zainstalować w swoim IDE. Będziesz popełniać mniej błędów, a przy okazji wyrobisz sobie wrażliwość na jakość kodu. Z czasem wiele rzeczy, na które SonarLint zwraca uwagę, stanie się dla Ciebie oczywistością i przestaniesz je nawet popełniać.

Jak uruchomić SonarQube lokalnie (Docker)

Skoro w teorii wiemy już sporo, czas na praktykę. Pokażemy teraz, jak krok po kroku uruchomić lokalnie serwer SonarQube przy użyciu Dockera. Dzięki temu będziesz mógł/mogła samodzielnie przeanalizować swój projekt i zobaczyć, jak SonarQube ocenia Twój kod. Załóżmy, że masz już zainstalowanego Dockera na swoim komputerze:

  1. Pobierz obraz SonarQube: Otwórz terminal i wykonaj polecenie:
    docker pull sonarqube:latest
    

    Spowoduje to pobranie najnowszej wersji obrazu SonarQube (Community Edition) z Docker Hub. To może chwilę potrwać, bo obraz ma kilkaset megabajtów.

  2. Uruchom kontener SonarQube: Gdy obraz jest pobrany, uruchom SonarQube w kontenerze za pomocą polecenia:
    docker run -d --name sonarqube -p 9000:9000 sonarqube:latest
    

    To polecenie tworzy i uruchamia kontener w tle (-d od detached) o nazwie sonarqube, mapując port 9000 kontenera na port 9000 Twojego komputera. Po kilku sekundach SonarQube powinien wystartować wewnątrz Dockera.

    Uwaga: SonarQube potrzebuje trochę czasu (zazwyczaj kilkadziesiąt sekund do minuty) na pełne uruchomienie, więc nie panikuj, jeśli od razu nie będzie dostępny.

  3. Sprawdź, czy działa: Możesz skorzystać z docker ps -a, aby upewnić się, że kontener SonarQube jest uruchomiony (status Up). Jeśli wszystko przebiegło pomyślnie, czas przejść do kolejnego kroku.
  4. Wejdź na interfejs SonarQube: Otwórz przeglądarkę i przejdź pod adres http://localhost:9000. Powinien ukazać się ekran logowania SonarQube. Domyślne dane logowania to: login: admin, hasło: admin. Po zalogowaniu zobaczysz główny dashboard SonarQube.

    Porada: Jeśli strona nie chce się załadować albo kontener się wyłącza, przyczyną może być zbyt mała pamięć przydzielona dla Dockera. SonarQube jest dość zasobożerny (uruchamia wewnętrznie silnik Elasticsearch). Możesz spróbować ponownie uruchomić kontener z dodatkowym parametrem zwiększającym limity, np. -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true (wyłącza sprawdzanie minimalnej pamięci przez Elasticsearch). Jednak najprostszym sposobem jest upewnienie się, że Docker ma przydzielone co najmniej ok. 2GB RAM.

  5. Analiza własnego projektu: SonarQube sam z siebie niczego nie analizuje, dopóki mu tego nie zlecimy. Jak to zrobić? Najczęściej wykorzystuje się do tego tzw. Sonar Scanner albo wtyczki do narzędzi buildujących (np. Maven, Gradle). Opis pełnej procedury mógłby zająć osobny artykuł 😉, ale w skrócie: musisz dostarczyć SonarQube kod do analizy. Przykładowo, w projekcie Java mavenowym dodaje się plugin Sonara i wykonuje komendę mavenową mvn sonar:sonar, która przeskanuje projekt i wyśle wyniki do naszego działającego serwera SonarQube. Alternatywnie, możesz zainstalować SonarScanner CLI i uruchomić go z parametrami wskazującymi lokalny kod oraz adres serwera. Gdy wykonasz analizę, odśwież stronę SonarQube – powinien pojawić się Twój projekt z wynikami (błędami, metrykami, itp.).

Na potrzeby startu nie musisz od razu analizować dużych projektów. Możesz utworzyć malutki projekt z kilkoma klasami, celowo dodać jakieś błędy (np. zostawić nieużywaną zmienną, może wstrzyknąć gdzieś potencjalnego NPE) i sprawdzić, czy SonarQube je znajdzie. Eksperymentuj śmiało – to lokalna instancja, niczego nie zepsujesz, a nauczysz się, jak narzędzie reaguje na różne konstrukcje w kodzie.

Gdy skończysz zabawę, kontener SonarQube możesz zatrzymać komendą docker stop sonarqube. Jeśli już go nie potrzebujesz, usuń kontener (docker rm sonarqube) oraz obraz (docker rmi sonarqube:latest), żeby zwolnić miejsce – w końcu zawsze możesz go ponownie pobrać później.

Analiza statyczna kodu a code review

SonarQube statyczna analiza kodu, przegląd kodu

Na koniec porozmawiajmy o ważnej kwestii: czy automatyczna analiza statyczna kodu zastępuje code review, czyli manualny przegląd kodu przez innego programistę? Krótka odpowiedź brzmi: nie, ale… doskonale uzupełnia pracę człowieka.

Analiza statyczna jest niezastąpiona w wyłapywaniu wielu oczywistych błędów i niezgodności ze standardami. Działa szybko i systematycznie – przeskanuje każdy plik, każdą linijkę, według zadanych reguł. Jednak automat ma swoje ograniczenia. Narzędzie nie zrozumie kontekstu biznesowego Twojej aplikacji, nie oceni, czy dany algorytm jest optymalny dla rozwiązania problemu, albo czy nazwy funkcji rzeczywiście oddają ich intencje. Tu wkracza ludzki czynnik, czyli code review.

Code review to praktyka, gdzie inny programista (często ktoś bardziej doświadczony, ale niekoniecznie – ważne, że świeżym okiem) przegląda Twój kod. Dzięki temu może wychwycić rzeczy, których maszyna nie złapie: np. czy dany fragment jest czytelny dla człowieka, czy rozwiązanie nie duplikuje funkcjonalności, czy spełnia wymagania zadania. Reviewer może też zobaczyć szerszy obraz: „Hej, a czy przypadkiem ten moduł nie powinien być częścią innej klasy?” – to są uwagi architektoniczne, designowe, których żaden automat nie podpowie.

➡ ZOBACZ 👉: Code Review – Nie wiesz jak pisać lepszy kod? Skup się na code review (przegląd kodu)!

➡ ZOBACZ 👉: Git pull request, GitHub pull request

W idealnym procesie tworzenia oprogramowania łączymy obie metody. Najpierw kod przechodzi automatyczną analizę statyczną (np. właśnie przez SonarQube czy SonarLint u każdego na etapie pisania). Dzięki temu wiele potencjalnych baboli jest już usuniętych. Następnie kod trafia na code review do osoby z zespołu, która nie musi tracić czasu na wytykanie brakującego przecinka czy formatowania, tylko skupia się na meritum. Takie dwustopniowe sito daje najlepsze efekty – kod jest zarówno poprawny pod kątem technicznym, jak i przemyślany pod kątem jakości rozwiązania.

W praktyce zespoły ustalają sobie, co automat ma sprawdzać, a co jest pozostawione dla code review. Przykładowo, można przyjąć zasadę: „Build przejdzie dalej tylko, jeśli SonarQube nie znajdzie błędów krytycznych i bezpieczeństwa oraz nie przekroczymy określonej liczby code smell” – to są twarde kryteria automatyczne. Natomiast podczas code review bardziej patrzymy na logikę, styl, nazewnictwo, zgodność z wymaganiami, itp. Obie formy feedbacku są ważne.

Warto tu wspomnieć, że w ramach mentoringu „Sprawny Programista Java” uczestnicy pracują nad jakością kodu wykorzystując SonarQube oraz regularne code review. Uczą się tym samym, jak efektywnie posługiwać się narzędziami do automatycznej analizy, a jednocześnie doskonalą umiejętność czytania i oceny kodu innych osób. Taka kombinacja rozwija wyczucie czystego kodu i buduje dobre nawyki od początku kariery.

>> Odbierz szkolenie: 
„Od Junior Java Developera do Mida w rekordowym czasie”

Podsumowanie

Statyczna analiza kodu to niezwykle przydatna technika, która pomaga pisać lepszy kod od pierwszych linijek. Narzędzia takie jak SonarQube i SonarLint działają jak dodatkowa para oczu – wychwytują błędy, podpowiadają ulepszenia i uczą dobrych praktyk. Szczególnie dla początkujących programistów stanowią ogromne wsparcie, bo pozwalają unikać typowych potknięć i szybko się na nich uczyć.

Pamiętaj jednak, że automatyczna analiza to nie wszystko. Najlepsze rezultaty osiągniesz, łącząc ją z manualnym code review i ciągłym doskonaleniem swoich umiejętności. Dzięki temu Twój kod będzie nie tylko wolny od oczywistych błędów, ale też dobrze przemyślany i zrozumiały dla innych.

Na koniec zachęcam: wypróbuj samodzielnie SonarQube i SonarLint w swoim projekcie. Zobacz, jakie sugestie da Ci narzędzie i zastanów się nad nimi. Każda poprawka to krok w stronę czystego, profesjonalnego kodu. Powodzenia w analizowaniu i doskonaleniu swojego kodu!

No comments
Share:
Testowanie oprogramowania

Testowanie oprogramowania

Czy wiesz, że istnieje jeden sprytny sposób na poprawienie jakości Twojego kodu, który pozwoli Ci dodatkowo szybciej uczyć się nowych bibliotek?

Co bardzo ważne, nie jest to żadna tajemna wiedza dla nielicznych, którą można posiąść jedynie na prywatnych szkoleniach za ciężkie pieniądze.

Sam stosuję tę metodę od wielu lat i dzięki niej z powodzeniem nauczyłem się dziesiątek nowych bibliotek i frameworków oraz wyłapałem większość błędów w moim kodzie – jeszcze zanim kod opuścił mój komputer i mógł narazić na szwank aplikacje moich klientów oraz moje dobre imię jako programisty.

Brzmi interesująco?

Z tego wpisu  dowiesz się:

  • jak zmniejszyć ilość błędów w Twoim oprogramowaniu – a czasami nawet pozbyć się ich całkowicie,
  • jak z przyjemnością uczyć się nowych bibliotek – dodatkowo zostanie Ci cały zbiór gotowych przykładów do wykorzystania w przyszłości,
  • jak bezpiecznie wprowadzać zmiany w Twoim kodzie i rozwijać aplikację, by jednocześnie nie stracić obecnych klientów!

Wielka prośba

Bardzo mocno napracowałem się, żeby przygotować tę serię, dlatego mam do Ciebie gorącą prośbę: proszę, pomóż mi dotrzeć z tym materiałem do innych osób, którym ta wiedza może pomóc.

Może wśród znajomych masz kogoś, komu warto podesłać ten artykuł? Podeślij mu link do tego materiału mailem lub udostępnij go na Facebooku.
Z góry bardzo, bardzo Ci dziękuję.

A teraz zapraszam do lektury. Zachęcam, byś na moment czytania skupił się tylko na tym, ponieważ zapewniam Cię, że materiał jest tego wart.

Po co testujemy nasz kod? 🤔

Zanim będziemy mogli przejść dalej, musimy najpierw ustalić nasze priorytety oraz wiedzieć, co chcemy osiągnąć.

Decydując się na pisanie testów, najczęściej chcemy uzyskać jeden z poniższych celów – choć, jak się za chwilę przekonasz, pisanie testów ma również kilka bardzo miłych efektów ubocznych.

Powód# 1. Automatyczne testowanie oprogramowania pomaga poprawić stabilność i jakość naszego rozwiązania

Ten punkt wymaga wyjaśnienia. Większość aplikacji prędzej czy później dochodzi do takiego poziomu skomplikowania, że nawet sam ich autor nie jest w stanie spamiętać wszystkich swoich założeń oraz zmian dokonanych w kodzie. Dodatkowo zdarzają się przecież różnego rodzaju pomyłki czy błędy – ostatecznie jesteśmy tylko ludźmi.

Kto to Panu tak s…?

Git blame
Git blame

Jeżeli masz co do tego jakieś wątpliwości, to postaraj się znaleźć jakiś fragment kodu, który napisałeś możliwie jak najdawniej. Ja niestety w większości wypadków mam duże problemy z przypomnieniem sobie, co miałem na myśli, pisząc dany fragment kodu, już po kilku tygodniach, a czasem i szybciej.

Zabawne jest, gdy widzisz jakiś fragment kodu, który napisany jest, mówiąc delikatnie, bez poszanowania dobrych praktyk i który nie ma prawa działać, a gdy sprawdzasz git blame, żeby zweryfikować, kto jest jego autorem, okazuje się, że to Ty sam…

Praca w niestabilnym środowisku

Niestabilne środowisko
Niestabilne środowisko

A teraz wyobraź sobie, że osób rozwijających daną aplikację jest więcej. Nie jedna, a np. dziesięć lub sto, a może nawet więcej. W świetle dzisiejszego tempa rozwoju IT to nic nadzwyczajnego.

W tak skomplikowanym, różnorodnym i zarazem niestabilnym środowisku jako programiści potrzebujemy jakiegoś narzędzia, które pozwoli nam potwierdzić, że nasz kod, nasza aplikacja zachowuje się tak, jak tego chcieliśmy.

Z pomocą przychodzą nam testy automatyczne. To one są niejako strażnikiem jakości naszej aplikacji. Przy ich pomocy najpierw deklarujemy określone założenia, czyli to, jak chcemy, by nasza aplikacja się zachowywała, a potem tylko cyklicznie sprawdzamy, czy dalej tak jest.

Powód# 2. Testy pozwalają również bezpieczniej wprowadzać zmiany w naszym kodzie

Ludzie z natury są raczej leniwi i w większości wypadków asekuracyjni – programiści również. Brak automatycznych testów bardzo często prowadzi do zjawiska tak zwanego gnijącego kodu.

Polega to na tym, że ze strachu przed zepsuciem obecnej funkcjonalności unikamy zmian w danym fragmencie kodu lub jeżeli zmiana jest konieczna, ograniczamy się do totalnego minimum, a potem szybko zamykamy problematyczną klasę i staramy się zapomnieć o niemiłym przeżyciu.

O porządnym refaktorze raczej nie ma co marzyć. Bardzo rzadko zdarza się, że ktoś się na to zdecyduje. W efekcie taki gnijący kod się rozrasta, a jego jakość tylko się pogarsza przy każdej kolejnej zmianie.

Brzmi znajomo? Ja niestety spotkałem się z takim efektem już w wielu projektach.

Rozwiązanie tego problemu jest naprawdę bardzo proste – wystarczą automatyczne testy! I odrobina dobrej woli ze strony programisty…

Jeżeli programiści będą mieli narzędzie pozwalające im zweryfikować, że nie zepsuli danej funkcjonalności, to z dużo większym zaangażowaniem zaczną pracować nad poprawą jakości i czytelności kodu.

Tak mało i tak wiele zarazem – niestety w wielu projektach wciąż o tym zapominamy.

Myśl jak kod będzie wykorzystywany

Myśl jak kod będzie wykorzystywany

Powód# 3. Testy wymuszają myślenie o tym, jak kod będzie wykorzystywany

Pisząc test (przede wszystkim jednostkowy) do swojego kodu, jesteś niejako pierwszym jego klientem.

Takie działanie wymusza myślenie o tym, jak kod będzie wykorzystywany oraz jak będzie zachowywał się w różnych sytuacjach.

Pamiętaj jednak, że to nie testy poprawiają jakość oprogramowania, a deweloperzy!
Testy są tylko narzędziem w ich rękach. Dlatego samo ich napisanie nie sprawi, że nagle poprawi się jakość Twojego kodu. Jednak dzięki nim będzie Ci dużo łatwiej wyłapać różnego rodzaju błędy i smrodki (ang. code smells), które zrobiłeś, pisząc kod.

Powód# 4. Większa przewidywalność – czyli rób to dla swojego świętego spokoju oraz lepszego snu…

Większa przewidywalność jest ceniona nie tylko przez menadżerów. Również jest to ważne z punktu widzenia programistów. Nawet jeżeli Twoi przełożeni nie zwracają większej uwagi na jakość rozwiązania, to w Twoim własnym interesie jest dbanie o produkt, który dostarczasz. Lepsza jakość oprogramowania oznacza większą przewidywalność.

Jeżeli przyłożysz się do jakości swojego rozwiązania, to możesz oczekiwać, że Twój kod nie będzie co chwilę się wysypywał, a Ty nie będziesz zajęty gaszeniem pożarów.

Dzięki temu łatwiej będzie Ci:

  • szacować nowe zmiany,
  • wdrażać nowe funkcjonalności oraz
  • dotrzymywać uzgodnień/planów.

To wszystko spowoduje, że w oczach wielu będziesz po prostu wyglądał na profesjonalistę.

Powód# 5. Testy pozwalają w łatwy sposób uczyć się nowych technologii i bibliotek

Ja bardzo lubię wykorzystywać testy do nauki nowych technologii. Jest to jeden z moich ulubionych sposobów na naukę nowych rzeczy.

Jeżeli chcę poznać nową bibliotekę czy framework, to zazwyczaj zaczynam od przygotowania prostej aplikacji typu Hello World, a następnie po prostu ją testuję.

Zazwyczaj są to testy jednostkowe, gdzie w każdej kolejnej metodzie testowej sprawdzam działanie kolejnych funkcjonalności.

Ponieważ moim celem nie jest przetestowanie takiej biblioteki, a jedynie jej przyswojenie, moje testy nie muszą spełniać wszystkich formalnych wymogów stawianym testom.

Poniżej zamieszczam przykład takiego kodu, napisany dla testów parametryzowanych w JUNIT 5. Wrócimy do tego zagadnienia jeszcze w kolejnym wpisie.

public class ParameterizedTests {

	@ParameterizedTest(name = "Run #{index} with [{arguments}]")
	@ValueSource(strings = {"Arg-1", "Arg-2", "Arg-3"})
	void withValueSource(String word) {
		assertThat(word).isNotEqualTo("Arg-2");
	}

	@ParameterizedTest
	@CsvSource({
			"A1, 1, 1.5",
			"A2, 20, 2.5",
			"A3, 30.4, 30",
	})
	void withCsvSource(String a, int b, double c) {
		System.out.println(String.format("a: %s, b: %s, c: %s", a, b, c));
	}

	@ParameterizedTest
	@MethodSource("createWordsWithLength")
	void withMethodSource(String name, Person person) {
		assertThat(name).isEqualTo(person.getName());
	}

	static Stream<Arguments> createWordsWithLength() {
		return Stream.of(
				Arguments.of("Ala", new Person("Ala", "XXX", 10)),
				Arguments.of("Tomek", new Person("Tomek", "W", 30))
		);
	}
}

Ogromną zaletą tego rozwiązania jest jego prostota oraz przykłady, które zostają mi na koniec nauki. Tak przygotowane wzorce w przyszłości wykorzystuję jako referencyjną implementację i jeżeli tylko mam jakieś wątpliwości, jak coś zaimplementować, to wracam do nich.

➡ ZOBACZ 👉:
Jeżeli interesuje Cię ten temat, przeczytaj również poniższe artykuły:

Powód# 6. Dzięki testom zweryfikujesz założenia oraz unikniesz pomyłek swoich i innych

Nie ma ludzi nieomylnych – każdemu zdarzają się jakieś błędy. Jednak w IT pod tym względem mamy jeszcze gorzej! Bardzo często nie rozpatruje się, czy jakiś programista popełnił błąd lub – jeżeli mówimy o oprogramowaniu – czy jakaś aplikacja zawiera funkcjonalności do poprawy.

Dużo częściej natomiast zadaje się pytanie – ile tych błędów jest?

Jest to o tyle powszechny problem, że zazwyczaj ilość błędów mierzy się różnymi metrykami, np.

  • ilość zgłoszeń przypadających na jeden przypadek użycia (czyli fragment funkcjonalności),
  • ilość błędów krytycznych (czyli takich, które uniemożliwiają skorzystanie z danej funkcjonalności) zgłoszonych w danym sprincie (np. okresie dwóch tygodni) itp.

Wprowadza się również różnego rodzaju obwarowania na czas reakcji po znalezieniu błędu, np. błąd krytyczny musi być poprawiony w przeciągu 24 h, a błędy mniej znaczące – do tygodnia.

Zobacz, że wszystkie te mechanizmy nawet nie rozważają, czy błędy wystąpią, czy nie – tylko z góry zakładają, że tak będzie! W przypadku programowania to normalna sytuacja i ciężko z nią polemizować. Jednak to od nas w dużej mierze zależy, czy współczynniki błędów będą trzymały się na dość niskim, akceptowalnym poziomie, czy wręcz przeciwnie.

Zastanówmy się teraz chwilę, jak możemy to osiągnąć.

Weryfikuj założenia i unikaj drobnych pomyłek

Zacznijmy od czegoś prostego, na co mamy wpływ – czyli od weryfikacji własnych założeń.

Przyjrzyj się poniższemu fragmentowi kodu i zastanów się, jaką wartość będzie miała zmienna formattedDate.

DateFormat dateFormat = new SimpleDateFormat();
Date date = new Date(2019, 1, 10);

String formattedDate = dateFormat.format(date);

Odpowiedzią możesz podzielić się w komentarzu pod tym artykułem.

Jako ciekawostkę zdradzę Ci, że zadałem to pytanie łącznie już kilkudziesięciu początkującym programistom i do tej pory nie trafiłem na osobę, która za pierwszym razem udzieliłaby prawidłowej odpowiedzi.

Dziwne? Moim zdaniem wcale nie! Ten kod nie jest intuicyjny i bez uruchomienia go, czyli przetestowania jego działania, BARDZO ciężko jest podać prawidłową odpowiedź.

I właśnie dokładnie o to chodzi – bardzo często zakładamy, że coś działa w ten, a nie inny sposób i później tego nie weryfikujemy tak długo, aż ktoś zwróci nam uwagę, że to nie działa.

Pamiętaj o innych – nie wszystko zależy od Ciebie

  • uważaj na kolegów z zespołu,
  • uważaj na zewnętrzne biblioteki i frameworki,
  • uważaj na samą Javę!

Weryfikuj również założenia, które zrobił ktoś inny – bo on też może się mylić.

Dlaczego nie piszemy testów❓

Skoro to wszystko jest takie piękne i proste, to czemu nie piszemy testów? Przyjrzyjmy się teraz kilku popularnym wyjaśnieniom, dlaczego w projekcie nie ma tylu testów, ile powinno być.

Część z nich to oczywiście tylko mity spowodowane w dużej mierze niewiedzą lub brakiem siły przebicia, jednak, jak to zazwyczaj bywa, część wymówek kryje ziarno prawdy.

Testy to koszty tu i teraz

Testy to koszty tu i teraz

Kto ma dziś czas na testy jednostkowe? Testy to koszty tu i teraz

Nie ma co się oszukiwać – napisanie testów wiąże się z pewnym wysiłkiem, do którego realizacji potrzebujemy umiejętności oraz czasu. Jednak pisanie testów oprogramowania można traktować jako swoistą inwestycję. To prawda, że będziemy musieli ponieść pewne koszty tu i teraz, możemy jednak liczyć na zwrot z tej inwestycji w przyszłości.

Mając taką wiedzę, część menadżerów podejmuje świadomie decyzję o ograniczeniu testów do minimum. Dzieje się tak najczęściej, gdy jakość końcowego produktu nie jest priorytetem, a zależy nam głównie na szybkości jego dostarczenia. Ewentualnie wiemy, że to nie my będziemy później utrzymywać taki produkt – wtedy niech martwi się już kto inny…

Teoretycznie zwiększony czas developmentu

Jak powiedzieliśmy sobie przed chwilą – pisanie testów zajmuje trochę czasu. Nie znaczy to jednak, że ten wysiłek nie zwróci nam się w dłuższej perspektywie czasu. Śmiem twierdzić, że odpowiednie testowanie oprogramowania może nie tylko uchronić nas przed źle wyglądającymi w oczach klienta wpadkami, ale również oszczędzić nam masę czasu.

Zastanów się chwilę, jaki wysiłek musisz ponieść, żeby naprawić błędnie działającą funkcję, gdy wykryjesz w niej nieprawidłowości:

  • podczas jej testowania lokalnie na Twoim komputerze,
  • na środowisku testowym przez testerów,
  • na środowisku produkcyjnym przez klienta.

Każdy kolejny poziom zazwyczaj wiąże się kłopotami o cały rząd wielkości większymi niż poprzedni. Jeżeli taki błąd zostanie wykryty dopiero produkcyjnie, może się również okazać, że spowodował on nieodwracalne szkody w danych! W takiej sytuacji nie tylko musimy wytropić i naprawić takiego buga, ale również zająć się naprawą błędnych lub brakujących danych – a to, jak możesz się domyślać, może zająć bardzo dużo czasu lub nie być wcale możliwe do realizacji.

Szczególnie początki są trudne

Szczególnie początkowy okres projektu związany jest z większymi kosztami. Spowodowane jest to głównie dwoma czynnikami:

  • potrzebą przygotowania i skonfigurowania całej infrastruktury do testów, czyli odpowiednich szablonów projektów, pluginów obsługujących testy czy dedykowanych środowisk, oraz
  • brakiem wypracowania odpowiednich standardów i dobrych praktyk testowania w zespole. Tu również można dołączyć brak wiedzy developerów z dziedziny testowania oprogramowania.

Wraz z rozwojem projektu na powyższe punkty będziemy poświęcać coraz mniej czasu, nie można jednak zapomnieć o stałych kosztach, które wiążą się z testami.

Takie testy trzeba utrzymywać i aktualizować. Każda kolejna zmiana w funkcjonalności będzie wymagała również naniesienia poprawek na system testujący. Jeżeli będziemy mieli dedykowane środowisko przeznaczone do testowania aplikacji, to takie serwery też trzeba utrzymywać i opłacać.

Presja kierownictwa

Presja kierownictwa

Presja kierownictwa

Strach kierownictwa przed zwiększonymi kosztami jest jak najbardziej zrozumiały. Do ich zadań należy dopilnowanie, by produkt o określonej jakości został dostarczony w założonym czasie do klienta (nawet jeżeli klientem jest nasza wewnętrzna firma) przy zachowaniu odpowiedniego budżetu!

Wielokrotnie spotkałem się z sytuacjami, że oczekiwano ode mnie lub od innych członków zespołu, że dana funkcjonalność lub poprawka ma być dostarczona na teraz lub, najlepiej, na wczoraj…

Jeżeli serwer produkcyjny nie działa, klient grozi nam karami, bo sam jest zablokowany ze swoim biznesem, a my właśnie pracujemy nad poprawką, to – pomijając cały związany z tym stres – nie jest to najlepszy moment na pracowanie nad wysublimowanym rozwiązaniem i pięknymi testami. W takiej sytuacji wszystkie ręce na pokład i bawimy się w strażaka, gasząc ten pożar!

Na dokładne napisanie testów oraz zastanowienie się, co poszło nie tak, przyjdzie jeszcze czas – gdy opanujemy sytuację.

To Ty jesteś specjalistą!

Z mojego punktu widzenia problem zaczyna się jednak, gdy taka sytuacja nie jest niczym nadzwyczajnym, a my jako codzienną rutynę traktujemy ratowanie sytuacji i gaszenie pożarów.

Niestety w takich warunkach nie ma kiedy przysiąść i naprawić daną funkcjonalność tak, by dany problem już nie wracał. Jeżeli tego nie zrobimy, a będziemy jedynie łatać produkt „tymczasowymi” łatkami, to dług technologiczny będzie się tylko zwiększał, przez co podobne sytuacje będą występowały tylko częściej i częściej.

W takiej sytuacji pamiętaj, że to Ty jesteś specjalistą – i to Ty w dużej mierze odpowiadasz za jakość tego, co dostarczasz. Jeżeli nie jesteś w stanie zapewnić odpowiedniej jakości, musisz to głośno i wyraźnie komunikować, by wspólnie móc się zastanowić nad rozwiązaniem tego problemu.

Crunch 😱

Crunch to, mówiąc w dużym uproszczeniu, wyciskanie ostatnich soków z programisty, żeby napisał jeszcze kilka linijek kodu. Całe szczęście nie jest to zbyt powszechna praktyka w naszej branży. Jeżeli jednak występuje, to o wysokiej jakości wytworzonego w ten sposób oprogramowania można tylko pomarzyć.

Jakiś czas temu podzieliłem się swoimi doświadczeniami na ten temat na blogu JavaDevMatt – tutaj jedynie przytoczę tamtą wypowiedź.

Z cranchem w całym tego słowa znaczeniu spotkałem się do tej pory tylko w jednym projekcie. Dzisiaj wiem, że zawczasu nie dopuściłbym do takiej sytuacji.

Warunki pracy w projekcie oczywiście z dnia na dzień nie stały się tak krytyczne. Początkowe nadgodziny (wszystkie były płatne) nawet mnie cieszyły. Dodatkowe pieniądze przecież zawsze się przydadzą. Miałem również okazję się wykazać i szybciej nauczyć nowych rzeczy. Co na tym etapie programistycznej kariery było dla mnie wyjątkowo ważne.

Prawdziwe problemy zaczęły się, gdy otrzymałem do realizacji bardziej wymagające zadania. Więcej osób polegało teraz na mojej pracy, a proces był tak zrobiony, że nawet stosunkowo krótka moja niedostępność mogła blokować pracę innym. Spowodowało to całkowitą utratę prywatnego czasu. W tym okresie telefony od współpracowników o 3–4 w nocy nikogo już nie dziwiły. Ba. Sam wielokrotnie po takim telefonie siadałem do komputera i gasiłem pożary. W okresie wydania nowej wersji dość często bywało, że w pracy spędzaliśmy bez przerwy ponad 24 h. Przy takim trybie pracy bywało, że niektórzy zwyczajnie przysypiali na klawiaturze.

Powstały w ten sposób produkt był wyjątkowo niskiej jakości. Zawsze było coś do szybkiego naprawienia, co blokowało pracę innym. Dlatego nie było kiedy przeprowadzić niezbędnych refaktorów czy napisać dobrych testów. Co w perspektywie czasu tylko pogarszało sytuację. Bywało również, że dane trzeba było naprawiać bezpośrednio na produkcji, by skrócić czas wdrożenia. Nie było to zbyt bezpieczne.

Z perspektywy czasu patrząc na to, najbardziej przerażające było to, że po pewnym czasie współpracownicy sami podjudzali tę sytuację. Pokutowała myśl, że skoro ja tak pracuję, to czemu Ty masz mieć lepiej… Chwilami było bardzo nerwowo i nieprzyjemnie. Niestety przełożeni godzili się na taką sytuację, teoretycznie zasłaniając się dobrem projektu i klienta. Prawda jednak była taka, że klient nie był zadowolony z efektów takiej współpracy, bo nowe wersje produktu otrzymywał niskiej jakości i notorycznie po terminie.

Dla mnie taki tryb pracy skończył się zmianą pracodawcy. Całe szczęście, po którejś nocnej pobudce żona postawiła veto dalszej takiej pracy. Zmiana okazała się bardzo dobrą decyzją, również pod względem finansowym, bo bez robienia licznych nadgodzin uzyskałem podobne wynagrodzenie.

Brak wiedzy i doświadczenia programistów

Mówi się, że brak znajomości prawa nie zwalnia z odpowiedzialności karnej. Chętnie sparafrazowałbym to na nasze warunki:

Brak umiejętności nie powinien zwalniać z obowiązku pisania testów i dostarczania kodu wysokiej jakości.

Niestety sami programiści bardzo często nie czują potrzeby pisania testów, często też nie uznają testów za element całego rozwiązania.

Przekonanie, że testerzy wyłapią wszystkie błędy

Jako osoby wytwarzające oprogramowanie zbyt często pokładamy wiarę w zewnętrzne testy. Dobry zespół testerski powinien wyłapać większość błędów i jest to oczywiście jego zadanie. Niestety nie zawsze tak jest. Testerzy nie zawsze mają odpowiednią wiedzę, żeby zweryfikować wszystkie potencjalne zależności, lub zwyczajnie też mogą mieć gorszy dzień i coś pominąć.

W tej relacji powinniśmy zdecydowanie kierować się zasadą obopólnego braku zaufania 🙂

Taki brak zaufania, który spowoduje dokładniejsze sprawdzanie naszego produktu, z pewnością przełoży się na jego jakość. Prawda jest taka, że dopiero połączone wysiłki programistów oraz testerów dają zadowalające wyniki.

Testy automatyczne

Testy automatyczne

Testy automatyczne – automatyzacja testów vs testy ręcznie (manualne) 🔍

Kolejnym zagadnieniem, które chciałbym przedyskutować, jest różnego rodzaju klasyfikacja testów. Skupimy się tylko na tych, z którymi będziemy mieli najczęściej do czynienia w projekcie, a mimo to ich lista jest całkiem długa.

Zacznijmy od podziału testów na ręczne oraz automatyczne.

Testy ręczne, nazywane również testami manualnymi, wykonywane są osobiście (czyli „ręcznie”) przez testerów, którzy muszą „przeklikać” się przez kolejne elementy aplikacji lub – wykorzystując do tego półautomatyczne narzędzia, jak np. Postman.
Tego typu testy są z reguły bardzo wolne i nie skalują się – praca za każdym razem musi być wykonywana od nowa.

Po drugiej stronie barykady są testy automatyczne, wykonywane przez maszynę – która tylko wykonuje wcześniej zaprogramowane kroki. Takie testy mogą sprawdzić tylko to, co wcześniej im zaprogramowaliśmy, czyli idealnie nadają się np. do testów regresji – gdzie weryfikujemy, czy dana funkcjonalność dalej działa.

Ich główną zaletą jest brak konieczności udziału człowieka w całym procesie, dzięki czemu możemy bardzo tanio, szybko i wielokrotnie testować aplikację na różnych środowiskach nawet w tym samym czasie.

Niestety w przypadku testów, gdzie niezbędne jest bardziej abstrakcyjne myślenie, jak np. testowanie całkowicie nowej funkcjonalności – najpierw musi zrobić to człowiek.

Osobiście jestem ogromnym zwolennikiem wszelkiego rodzaju automatyzacji. Bez dobrze przygotowanych testów automatycznych nie można myśleć o kolejnym kroku w usprawnianiu cyklu wytwarzania oprogramowania, czyli ciągłej integracji (CI – Continuous Integration) i ciągłym dostarczaniu (CD – Continuous Delivery).

Testy Postman

Testy Postman

Poziomy testów

Przyjrzyj się teraz i porównaj różne poziomy testów oprogramowania, do których zaliczymy przede wszystkim bardzo popularne testy jednostkowe i integracyjne, ale również testy end-to-end oraz testy manualne.

Poszczególne rodzaje testów mogą mieć za zadanie sprawdzanie całej aplikacji lub jej fragmentów pod różnym kątem oraz być ukierunkowane na odmienne cele.

Testy jednostkowe

Testy jednostkowe przeprowadzane są na bardzo niskim poziomie – przy samym kodzie źródłowym aplikacji. Testowane są poszczególne klasy lub nawet same metody.

  • działają w izolacji – testują jeden wybrany fragment logiki,
  • są z założenia bardzo szybkie i często uruchamiane.

Ale! Nawet 100% pokrycia testami jednostkowymi nie zapewnia poprawności działania systemu. Testy jednostkowe weryfikują jedynie, czy dany wycinek logiki działa poprawnie w odseparowaniu od reszty systemu. Nie sprawdzają one jednak komunikacji z innymi częściami aplikacji oraz poprawności całego procesu.

Testy integracyjne

Testy integracyjne weryfikują natomiast, czy kolejne komponenty aplikacji odpowiednio ze sobą współpracują. Na tym poziomie sprawdzamy, czy np. serwis odpowiednio przekazuje argumenty do bazy danych lub poszczególne mikrousługi komunikują się ze sobą zgodnie z założeniami.

  • testujemy kilka modułów systemu jednocześnie,
  • potrzebna jest tylko część architektury (zazwyczaj nie ma konieczności uruchamiania całej aplikacji).

Testowanie integracyjne sprawdza zachodzące interakcje pomiędzy testowanymi modułami oprogramowania. Takie testy z reguły działają trochę wolniej niż testy jednostkowe, ponieważ do ich uruchomienia potrzebujemy większej ilości komponentów aplikacji oraz części architektury, np. bazy danych.

Podczas realizacji testów integracyjnych można wykonać testy niektórych atrybutów niefunkcjonalnych na równi z testami funkcjonalnymi, można np. pokusić się o weryfikację wydajności czasowej czy zużycia pamięci.

Ale! Nawet 100% pokrycia testami integracyjnymi nie zapewnia poprawności działania systemu…

Testy integracyjne vs testy jednostkowe

Jeżeli chociaż przez chwilę przeszła Ci przez głowę myśl: po co mi testy integracyjne, skoro mogę wszystko przetestować jednostkowo – to poniższa galeria jest zdecydowanie dla Ciebie.

testy integracyjne vs jednostkowe
testy integracyjne vs jednostkowe

Testy end-to-end/systemowe

Testy end-to-end symulują zachowanie konkretnego użytkownika korzystającego z aplikacji. Do ich wykonania potrzebna jest pełna infrastruktura z w pełni działającą aplikacją.
Testy z tego poziomu niejako „przeklikują się” przez aplikację. Z technicznego punktu widzenia można je zrealizować np. przy pomocy Selenium.

  • emulowany użytkownik „klika” po realnym systemie,
  • potrzebny działający system – pełna infrastruktura,
  • drogie w napisaniu i utrzymaniu oraz bardzo wolne (w porównaniu do testów jednostkowych i integracyjnych).

Ale! Nawet 100% pokrycia testami end-to-end nie zapewnia poprawności działania systemu…

Manualne testy akceptacyjne

Celem testów akceptacyjnych jest nabranie zaufania do testowanego systemu, jego części lub tylko pewnych atrybutów niefunkcjonalnych – np. wydajności aplikacji. Sprawdzamy również, czy spełnia on stawiane przed nim wymagania biznesowe – czyli wymagania funkcjonalne.

Testy akceptacyjne mogą być realizowane w różnych cyklach życia oprogramowania, np. po wdrożeniu systemu na produkcję, ale również po pierwszym wdrożeniu nowej funkcjonalności na środowisko testowe.

Jeżeli przeprowadzamy testy na środowisku produkcyjnym lub preprodukcyjnym, to raczej już nie wyszukujemy nowych błędów, a jedynie oceniamy gotowość systemu do pełnego wdrożenia.

  • ostatni poziom testów – testy ręczne,
  • człowiek nie powinien robić tego, co może zrobić maszyna.

Człowiek nie powinien wykonywać ręcznej pracy, którą może wykonać maszyna – powinien MYŚLEĆ, co można usprawnić i co można zautomatyzować.

Piramida testów

Piramida testów

Piramida testów 🔺

Powiedzieliśmy sobie przed chwilą o różnych poziomach testów: o testach jednostkowych, integracyjnych, end-to-end oraz manualnych. Wszystkie one są potrzebne w projekcie, jeżeli zależy nam na skutecznym testowaniu oprogramowania.

Jednak sam fakt posiadania powyższych testów to jeszcze za mało – powinny one być ułożone w odpowiedni sposób, z zachowaniem prawidłowych zależności między poszczególnymi warstwami.

W literaturze możemy spotkać się z pojęciem zdrowej piramidy testów. Taka piramida w obrazowy sposób pokazuje, których testów powinniśmy pisać najwięcej, a których – najmniej.

Podstawą piramidy i najliczniejszą grupą są testy jednostkowe, których też przygotowanie będzie najszybsze i najtańsze. Trochę mniej powinno być testów integracyjnych oraz end-to-end. Natomiast najwolniejsze i zarazem najdroższe w przygotowaniu testy manualne znajdują się na samym szczycie piramidy – czyli powinno ich być stosunkowo najmniej.

Bardzo ważne jest jednak, by nie pominąć żadnej z warstw testowych piramidy, ponieważ – dopiero zapewniając testy na każdym poziomie – możemy się upewnić co do naszych założeń w stosunku do testowanej aplikacji.

Typy testów oprogramowania

Istnieje wiele różnych typów testów, które możemy wykorzystać podczas tworzenia oprogramowania w celu upewnienia się, że nasza aplikacja działa zgodnie z założeniami. W kolejnych akapitach przedstawię różnice pomiędzy najczęściej spotykanymi typami testów.

Poszczególne testy mogą być nakierowane na weryfikację systemu (lub jego część), bazując na pewnych celach, np.

  • przetestowanie funkcji wykonywanej przez oprogramowanie,
  • przetestowanie jakiegoś niefunkcjonalnego atrybutu jakościowego (jak niezawodność lub użyteczność),
  • przetestowanie struktury lub architektury systemu,
  • testy związane ze zmianami, czyli potwierdzeniem, że usterki zostały naprawione, i szukaniem niezamierzonych zmian (testowanie regresywne).

Testy funkcjonalne

Black-box test

Black-box test

Testy funkcjonalne znane są także jako testy czarnej skrzynki, ponieważ osoba testująca nie ma dostępu do informacji na temat budowy programu, który testuje – czyli przez analogię testuje czarną, zamkniętą skrzynkę.

Testy funkcjonalne pisane są pod kątem wymagań biznesowych, które ma spełniać testowana aplikacja. Takie testy weryfikują aplikację niejako z lotu ptaka – sprawdzany jest ostateczny wynik wykonywanej operacji, pomijane natomiast są wszystkie pośrednie stany i obliczenia, jakie musiał wykonać system.

Testy niefunkcjonalne (testowanie atrybutów niefunkcjonalnych)

Testowanie niefunkcjonalne obejmuje, między innymi, następujące typy testów:

  • testowanie wydajnościowe,
  • testowanie obciążeniowe,
  • testowanie przeciążeniowe,
  • testowanie użyteczności,
  • testowanie niezawodności oraz
  • testowanie przenaszalności.

Mówiąc w skrócie – testowanie niefunkcjonalne polega na sprawdzeniu, „jak” system działa.

White-box testowanie

Testy strukturalne (white-box testing)

White-box testowanieTesty strukturalne (białoskrzynkowe) można wykonywać na każdym poziomie testowania (znane są także jako testy białej lub szklanej skrzynki). Polegają na testowaniu programu poprzez podawanie na wejściu takich danych, aby program przeszedł przez każdą zaimplementowaną ścieżkę. Nierzadko w trakcie testowania programu techniką szklanej skrzynki wprowadzane są do wnętrza programu sztuczne, specjalnie spreparowane dane w celu dokładniejszego przetestowania reakcji.

Manifest 👇

W ramach podsumowanie tego wywodu chciałbym przedstawić swego rodzaju manifest, którego przestrzeganie z pewnością ułatwiłoby programistyczne życie niejednej osobie.

  1. Staraj się wprowadzać możliwie jak najmniejsze zmiany – stosując metodę ewolucji zamiast rewolucji, unikasz problemów związanych z tak zwanym wielkim wybuchem.
  2. Zawsze staraj się zostawić kod odrobinę lepszym, niż go zastałeś.
  3. Unikaj powtarzalnych zadań. Jeżeli tylko coś można zautomatyzować, to zrób to.
  4. Zadawaj właściwie pytania – przede wszystkim „dlaczego?”.

Podsumowanie – testowanie

Testowanie jest niezwykle ważnym elementem cyklu wytwarzania oprogramowania i niezależnie, czy jesteś pełnoetatowym testerem, czy programistą, z pewnością powinieneś się nad nim pochylić. W tym artykule zebrano bardzo dużo pomysłów i zdaję sobie sprawę, że na wdrożenie ich wszystkich potrzeba masę czasu – dlatego nie zniechęcaj się, wybierz sobie choć jedną małą rzecz i postaraj się zacząć właśnie od niej.

Powodzenia.
Tomek.

 

18 komentarzy
Share:

JUnit – Testy jednostkowe » tutorial dla bystrzaków (testy jednostkowe Java w JUnit 5)

Czy wiesz, dlaczego zawodowi programiści z kilkuletnim doświadczeniem popełniają tak mało błędów i są w stanie wypuszczać nowe wersje swojej aplikacji, nawet kilka razy dziennie i jednocześnie niczego w niej nie popsuć?
Magia? – No raczej niekoniecznie…😉

W dużej mierze jest to związane oczywiście z ich doświadczeniem i obyciem, jakie zdobyli przez te lata – jednak to nie wszystko. Bez wątpienia ważny jest również sam proces testowania, któremu poddawana jest aplikacja. Bez odpowiednich testów możemy jedynie mieć nadzieję i mocno trzymać kciuki, żeby nasz kod, który wytworzyliśmy, działał zgodnie z założeniami. Jednak zapewne jak się domyślasz, w zawodowym programowaniu nie do końca o to chodzi…

To jak? Chcesz wiedzieć więcej? 👋 💬

Jest to już drugi wpis z serii o testowaniu aplikacji. W poprzedniej części: ➡ Testowanie oprogramowania zrobiliśmy wprowadzenie do testowania z punktu widzenia developera, omówiliśmy typy testów i korzyści, jakie wynikają z ich stosowania. Jeżeli jeszcze nie czytałeś tego wpisu, to koniecznie nadrób zaległości – najlepiej jeszcze przed tym tekstem.


Testy jednostkowe

Piramida testów

Piramida testów

Testy jednostkowe są na najniższym poziomie zdrowej piramidy testów.

Co za tym idzie:

  • powinno być ich stosunkowo najwięcej w projekcie,
  • ich koszt przygotowania i utrzymania powinien być relatywnie najniższy
  • oraz powinny działać najszybciej.

W praktyce oznacza to, że testy jednostkowe są bardzo blisko kodu źródłowego naszej aplikacji. W poszczególnym teście jednostkowym testujemy pojedynczą klasę, czy nawet pojedynczą metodę.


JUnit

JUnit 5

JUnit 5

Dzisiaj skupimy się na najpopularniejszej obecnie bibliotece do testów jednostkowy oraz integracyjnych – JUnit.

Z tego tekstu dowiesz się:

  • co to są testy jednostkowe oraz dlaczego jako programista po prostu MUSISZ się nimi zainteresować;
  • do czego służy biblioteka JUnit oraz jak wykorzystać ją w Twoim projekcie,
  • oraz poznasz praktyczne przykłady, jak wprowadzić w życie dobre praktyki testowania jednostkowego kodu.

JUnit pierwszy prosty test

Pojedynczy test jednostkowy w JUnit to metoda oznaczona adnotacją @Test.

import org.junit.jupiter.api.Test;

public class ExamplesTest {
    @Test
    void emptyTestMethod(){
    }
}

Pamiętamy również o odpowiednim imporcie: import org.junit.jupiter.api.Test .

W naszym przykładzie do organizacji projektu wykorzystałem Maven, dlatego klasa zawierająca metodę testującą (przy zachowaniu domyślnej konfiguracji), powinna być w katalogu src/test/java.

Testy jednostkowe maven

Testy jednostkowe Maven

Mamy już pierwszą metodę testową – co prawda, jeszcze nic nie robi, ale i na to przyjdzie pora. Pusta metoda testująca, z technicznego punktu widzenia, jest również poprawnym testem – ponieważ nie określiliśmy żadnych warunków, które mają być spełnione, to taki test zawsze będzie zwracał informację, że wszystko jest 🆗. ✅


Jak uruchomić testy jednostkowe JUnit❓

Uruchommy teraz nasze testy.

Ja podczas pracy najczęściej wykorzystuję do tego IDE Intellij, jednak możliwości w tej kwestii jest całkiem sporo.

Testy jednostkowe Intellij

Testy jednostkowe Intellij

Testy jednostkowe Intellij

Testy jednostkowe Intellij

W Intellij IDEA możemy to zrobić, między innymi korzystając z:

  • menu kontekstowego kodu źródłowego, z którego poziomu możemy uruchomić wybraną metodę testową lub wszystkie metody z danej klasy;
  • niewielkich zielonych trójkącików, które znajdziesz przy nazwie metody lub klasy. Działają one analogicznie jak menu kontekstowe;
  • menu górnego Run, gdzie możemy zdefiniować np. przekazywane argumenty do maszyny wirtualnej Java podczas uruchomienia testów
  • oraz menu kontekstowego widoku Projekt, gdzie możemy np. uruchomić wszystkie testy z danego pakietu.

Niezależnie od sposobu, który wybierzesz – IDE powinno uruchomić wskazane testy i wyświetlić Ci mniej więcej taki raport z ich przebiegu.

JUnit Intellij IDEA

JUnit Intellij IDEA

Wiersz poleceń – testy jednostkowe JUnit

Kolejnym sposobem jest uruchomienie testów z poziomu linii komend.
Z tego rozwiązania najczęściej korzystam podczas przygotowywania skryptów pod CI (Continuous Integration), np. w Bamboo, Github Actions lub Jenkins.
Do uruchomienia wszystkich testów z danego projektu wystarczy uruchomienie poniższej komendy maven.
mvn test

W razie potrzeby możemy też doprecyzować, które testy chcemy uruchomić, np. mvn test -Dtest=ExamplesTest.

Na koniec powinniśmy zobaczyć raport z przebiegu Twoich testów.

JUnit konsola

JUnit konsola


JUnit tutorial

JUnit

JUnit

Przećwiczymy teraz w praktyce wykorzystanie testów jednostkowych na przykładzie usługi kalkulatora – kod usługi znajdziesz poniżej.

Mamy tutaj:

public class CalculatorService {
	public int add(String a, String b) {
		if (a == null || b == null) {
			throw new IllegalArgumentException("Arguments 'a' and 'b' are required.");
		}

		return Math.addExact(Integer.parseInt(a), Integer.parseInt(b));
	}
}

Teoretycznie bardzo prosta metoda – ot zwykły kalkulator. Jednak chcąc dobrze przetestować, nawet tak krótki fragment kodu, musimy uwzględnić bardzo dużo warunków, np.

  • tak zwany happy path, czyli podstawowy („szczęśliwy”) przepływ metody, gdy wszystko idzie zgodnie z planem,
  • warunki brzegowe, czyli bardziej problematyczne przypadki, np.:
    • walidację na przekazanie do metody niepoprawnych argumentów (do pierwszego argumentu, do drugiego oraz do obu jednocześnie),
    • co się stanie, gdy do metody zostanie przekazana wartość pusta NULL,
    • dodawanie wartości ujemnych,
    • bardzo duże wartości, które spowodują przekroczenie zakresu typu Integer,
    • itp. itd.

Jak widzisz, możliwości jest całkiem sporo. Jeżeli chcemy mieć pewność, że nasz kod zachowuje się w każdej z tych sytuacji zgodnie z naszymi oczekiwaniami, to musimy ustalić te warunki, a następnie przygotować testy z odpowiednimi asercjami, które je zweryfikują.

➡ ZOBACZ 👉:


JUnit Assert – asercja, czyli weryfikacja założeń

Dzięki asercjom jesteśmy w stanie weryfikować nasze założenia wobec kodu.

  • Poczynając od bardzo prostych, takich jak sprawdzenie, czy metoda zwróciła konkretną wartość liczbową,
  • do bardziej złożonych, takich jak sprawdzenie, czy zwrócony obiekt jest danego typu,
  • czy ostatecznie sprawdzenie, czy metoda rzuciła oczekiwany wyjątek.

Podstawowa asercja to wywołanie metody:
public static void assertEquals(int expected, int actual)
z klasy: org.junit.jupiter.api.Assertions.

class CalculatorServiceAddTest {

	@Test
	void shouldAddTwoIntegers() {
		int result = calculatorService.add("10", "20");

		Assertions.assertEquals(30, result);
	}
}

Jako pierwszy argument do metody assertEquals przekazujemy wynik, którego się spodziewamy, a jako drugi nasz aktualny wynik, jaki zwróciła metoda.

Jeżeli wszystko pójdzie zgodnie z założeniami, to program nie podejmie żadnej akcji i przejdzie do weryfikacji kolejnej asercji lub zakończy cały test, jeżeli to była ostatnia asercja.

W przypadku kiedy wynik nie będzie się zgadzał z oczekiwaniami, działanie programu zostanie przerwane (nie będą sprawdzane kolejne warunki!) i zobaczymy stosowny komunikat.

JUnit Assert

JUnit Assert

Błąd org.opentest4j.AssertionFailedError przerywa aktualnie wykonywany test i oznacza go jako niepoprawny.
W szczegółach błędu możesz doczytać, jaki jest dokładny powód niepowodzenia testu.

JUnit Assertions – assertEquals

JUnit Assertions – assertEquals

Metoda assertEquals jest przeciążona tak, że przyjmuje praktycznie dowolne typy argumentów.
Dodatkowo mamy do dyspozycji kilka innych analogicznych metod:

  • assertNotEquals – negacja assertEquals,
  • assertNull oraz assertNotNull – do porównywania wartości z wartością pustą NULL,
  • assertTrue oraz assertFalse – do porównywania wartości logicznych boolean,
  • assertArrayEquals – do porównywania tablic,
  • oraz metoda fail – której każde wywołanie automatycznie oznacza cały test jako niepoprawny.

Mimo iż zbiór asercji dostarczanych w pakiecie z JUnit jest całkiem pokaźny, to w praktyce bardzo często korzysta się z zewnętrznych bibliotek, które jeszcze rozszerzają ich możliwości.
Dwie obecnie najpopularniejsze biblioteki tego typu to Hamcrest oraz AssertJ – osobiście korzystam głównie z AssertJ.

AssertJ

Jeżeli już korzystamy z mavena, to dodanie biblioteki AssertJ do naszego projektu będzie wiązało się tylko z dodaniem nowej zależności.

<dependency>
	<groupId>org.assertj</groupId>
	<artifactId>assertj-core</artifactId>
	<version>3.20.2</version>
	<scope>test</scope>
</dependency>

Od tego momentu w naszych testach możemy już korzystać z dużo bardziej rozbudowanych assercji.

Na uwagę zasługuje przede wszystkim prostota i genialna wręcz użyteczność metody: Assertions.assertThat z pakietu: org.assertj.core.api.Assertions.

Metoda assertThat przeciążona jest z wykorzystaniem większości podstawowych typów i dla każdego z nich udostępnia zbiór specyficznych metod.
Przykładowo – jeżeli przekażemy do niej argument typu String, to będziemy mieli dostępną np. metodą: isNotEmpty().

String string = "Java";
assertThat(string).isNotEmpty();

Natomiast jeżeli jako argument przekażemy kolekcję, to możemy już weryfikować konkretne elementy tej kolekcji. Co w praktyce jest bardzo wygodne.

List<String> list = Arrays.asList("Ala", "zna", "Javę");
assertThat(list).containsExactly("Ala", "zna", "Javę");

Testy jednostkowe – dobre praktyki 👌

Przyjrzyjmy się teraz kilku najczęściej występującym dobrym praktykom związanym z testami jednostkowymi.

Zasady testowania jednostkowego FIRST

  • Fast – szybkie testy uruchamiane szybko, najlepiej bezpośrednio po dokonaniu zmian w kodzie,
  • Independent (Isolated) – każdy test powinien być niezależny i działać w izolacji od pozostałych. Nie może być sytuacji, w których działanie testu zależne jest od wcześniejszego wykonania innego testu,
  • Repeatable – stabilne wyniki przy każdym kolejnym uruchomieniu. Niezależnie, czy test przechodzi, czy też nie – jego wynik musi być stabilny. W przeciwnym wypadku nie będziemy mogli polegać na takich wynikach,
  • Self-checking – jednoznaczna i automatyczna informacja o niepowodzeniu testu. Korzystanie z asercji niejako automatycznie spełnia ten warunek. Nie może być sytuacji, w której do odkreślenia, czy warunki zostały spełnione, czy też niezbędne jest ręczne działanie człowieka,
  • Timely – aktualne oraz napisane w tym samym czasie co kod. Test, który jest nieaktualny i który nie sprawdza tego co powinien, jest niewiele wart.

JUnit – struktura testu (nie tylko) jednostkowego

Dość powszechną oraz polecaną praktyką jest dzielenie poszczególnych przypadków testowych na trzy sekcje:

  • given – przygotowanie wszystkich danych wejściowych,
  • when – uruchomienie testowanej metody,
  • then – weryfikacja otrzymanych wyników.
@Test
void shouldAddTwoIntegers() {
	// given
	String a = "10";
	String b = "20";

	// when
	int result = calculatorService.add(a, b);

	// then
	assertEquals(30, result);
}

Można w tym celu posiłkować się różnymi innymi rozwiązaniami, jak np. Cucumber, jednak jeżeli korzystamy z JUnit, to wystarczy w tym celu oznaczyć poszczególne sekcje stosownym komentarzem.

Nie jest to wymóg techniczny, a jedynie dobra praktyka, która poprawia czytelność naszych testów.


Testy jednostkowe w praktyce

Podsumujemy teraz zdobytą już przez nas wiedzę i dokończmy testowanie przykładowego kalkulatora.
W poniższym fragmencie kodu zawarłem wszystkie omawiane wcześniej przypadki testowe.

Zwróć uwagę, że

  • każdy przypadek testowy został zamodelowany jako osobna metoda z adnotacją @Test,
  • nazwą metody, która jednoznacznie sugeruje, jakie mamy oczekiwania wobec tego przypadku,
  • oraz z wyraźnym podziałem na trzy sekcje: given, when, then.

W poszczególnych asercjach posiłkowałem się biblioteką AssertJ.

public class CalculatorServiceTest {

	private CalculatorService calculatorService;

	@BeforeEach
	void beforeEach() {
		calculatorService = new CalculatorService();
	}

	@Test
	void shouldAddTwoCorrectNumbers() {
		// given
		String a = "10";
		String b = "20";

		// when
		int result = calculatorService.add(a, b);

		// then
		assertEquals(30, result);
	}

	@Test
	void shouldThrowExceptionOnInvalidFirstArgument() {
		// given
		String a = "wrong-number";
		String b = "10";

		// when
		Throwable throwable = catchThrowable(() -> calculatorService.add(a, b));

		// then
		assertThat(throwable)
				.isInstanceOf(NumberFormatException.class)
				.hasMessage("For input string: \"wrong-number\"");

	}

	@Test
	void shouldThrowExceptionOnInvalidSecondArgument() {
		// given
		String a = "10";
		String b = "wrong-number";

		// when
		Throwable throwable = catchThrowable(() -> calculatorService.add(a, b));

		// then
		assertThat(throwable)
				.isInstanceOf(NumberFormatException.class)
				.hasMessage("For input string: \"wrong-number\"");

	}

	@Test
	void shouldThrowExceptionOnEmptyFirstArgument() {
		// given
		String a = "";
		String b = "10";

		// when
		Throwable throwable = catchThrowable(() -> calculatorService.add(a, b));

		// then
		assertThat(throwable)
				.isInstanceOf(NumberFormatException.class)
				.hasMessage("For input string: \"\"");

	}

	@Test
	void shouldThrowExceptionOnIntegerOverFlow() {
		// given
		String a = Integer.MAX_VALUE + "";
		String b = "11";

		// when
		Throwable throwable = catchThrowable(() -> calculatorService.add(a, b));

		// then
		assertThat(throwable)
				.isInstanceOf(ArithmeticException.class)
				.hasMessage("integer overflow");
	}
}

JUnit integracja z IDE Intellij IDEA

Z testów jednostkowych możemy oczywiście korzystać z poziomu linii komend i notatnika, jednak posiłkując się dowolnym IDE, zapewne będzie nam się pracowało dużo sprawniej.

Podstawowa integracja, której na pewno warto się przyjrzeć to przynajmniej:

  • generowanie raportów z wyniku testów,
  • uruchamianie poszczególnych przypadków testowych całych klas, pakietów itp.,
  • oraz generowanie samych klas testowych.

Korzystając z automatycznych generatorów (żółta żarówka, która pojawi się po najechaniu na nazwę klasy) można wygenerować szablon klasy testowej na podstawie klasy, którą chcemy testować.


Cykl życia testów w JUnit

JUnit oferuje nam również wiele opcji jeżeli chodzi o możliwość wpłynięcia na przebieg naszych testów w różnych momentach ich cyklu życia.
Możemy np. uruchomić stosowny fragment kodu po (lub przed) każdej poszczególnej metodzie testującej oraz przed (lub po) wszystkimi naszymi metodami.
Co niewątpliwie jest bardzo praktyczne, kiedy np. chcemy przygotować i później posprzątać środowisko, żeby przeprowadzić nasz test.

public class TaskJUnit {

	@BeforeAll
	static void beforeAll() {
		System.out.println("beforeAll");
	}

	@BeforeEach
	void beforeEach() {
		System.out.println("beforeEach");
	}

	@Test
	void testMethod1() {
		System.out.println("testMethod1");
	}

	@Test
	void testMethod2() {
		System.out.println("testMethod2");
	}

	@AfterEach
	void afterEach() {
		System.out.println("afterEach");
	}

	@AfterAll
	static void afterAll() {
		System.out.println("afterAll");
	}
}

Po uruchomieniu powyższego kodu widzimy, w jakiej kolejności zostaną uruchomione poszczególne metody.

JUnit – cykl życia testu

JUnit beforeAll

Metoda zostanie uruchomiona PRZED wszystkimi metodami testowymi w danej klasie – a co za tym idzie, musi zostać oznaczona jako statyczna.
Niezależnie od ilości testów w ramach naszej klasy z testami, taka metoda zostanie uruchomiona tylko raz.

JUnit afterAll

Metoda zostanie uruchomiona PO wszystkich metodach testowych w danej klasie – a co za tym idzie, musi zostać oznaczona jako statyczna.
Niezależnie od ilości testów w ramach naszej klasy z testami, taka metoda zostanie uruchomiona tylko raz.

JUnit beforeEach

Metoda zostanie uruchomiona PRZED każdą metodą testową w obrębie danej klasy. Metoda zostanie uruchomiona tyle razy ile mamy takich metod.

JUnit after Each

Metoda zostanie uruchomiona PO każdej metodzie testowej w obrębie danej klasy. Metoda zostanie uruchomiona tyle razy ile mamy takich metod.


JUnit Maven – instalacja JUnit przez zależności maven

<dependency>
	<groupId>org.junit.jupiter</groupId>
	<artifactId>junit-jupiter-engine</artifactId>
	<version>${junit.jupiter.version}</version>
</dependency>
<dependency>
	<groupId>org.junit.platform</groupId>
	<artifactId>junit-platform-runner</artifactId>
	<version>${junit.platform.version}</version>
</dependency>	

JUnit Parameterized – testy parametryzowane

JUnit oferuje wiele różnych rozszerzeń, a jednym z popularniejszych i bardziej przydatnych są z pewnością testy parametryzowane.
Ich wykorzystanie pomaga w rozdzieleniu przygotowania danych testowych od samych testów. Z pewnością jest to szczególnie przydatne gdy chcemy np. przeprowadzić bardzo podobne testy, ale dla całkowicie różnych danych wejściowych.

Jeżeli to jest Twój przypadek, to warto zainteresować się takimi adnotacjami jak @ParameterizedTest oraz @ValueSource i @MethodSource.

@ParameterizedTest
@ValueSource(strings = {"Arg-1", "Arg-2", "Arg-3"})
void valueSource(String param) {
	System.out.println(param);
}

@ParameterizedTest
@EnumSource(value = DayOfWeek.class)
void enumSource(DayOfWeek day) {
	System.out.println(day);
}

@ParameterizedTest
@MethodSource("stringProvider")
void methodSource(String param) {
	System.out.println(param);
}

static Stream<String> stringProvider() {
	return Stream.of("Ala", "zna", "Javę");
}

Dodatkowe materiały do nauki (nie tylko testów) 📚


Testy jednostkowe – co dalej? 🤔

Uff. 🙂 To było całkiem niezłe wprowadzenie do testów jednostkowych i mam nadzieję, że bazując na zgromadzonych tutaj przykładach uda Ci się wprowadzić testy w Twojej aplikacji. Do czego swoją drogą gorąca zachęcam, bo jest to jedna z lepszych inwestycji, jakie możemy zrobić dla naszych aplikacji.

Czy to koniec informacji, jakie warto poznać w kontekście testowania kodu?

Zdecydowanie NIE! Tak naprawdę to dopiero początek… 🙂
Jako kolejny krok proponuję zapoznać się z mockami, TDD oraz integracją testów z cyklem życia naszych aplikacji (CI).

Jeżeli interesują Cię takie tematy, to zapraszam na KierunekJava.pl, gdzie w cotygodniowym newsletterze staram się rozłożyć Javę na czynniki pierwsze! 💪

Pozdrawiam i do usłyszenia!
Tomek

3 komentarze
Share: