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!

Spis treści

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.

 


20+ BONUSOWYCH materiałów z programowania

e-book – „8 rzeczy, które musisz wiedzieć, żeby dostać pracę jako programista”,
e-book – „Java Cheat Sheet”,
checklista – „Pytania rekrutacyjne”
i wiele, wiele wiecej!

Jak zostać programistą

[su_button url="/ebook/?r=1&utm_campaign=w_pt" target="blank" style="soft" size="8" center="yes" icon="icon: download" desc="e-book" title="Pobieram darmowy e-book"]Odbieram Dostęp Do E-Booków![/su_button]

18 komentarzy
Share:

18 Comments

  1. SEBASTIAN says:

    Wpis bardzo obszerny w praktyczne wiadomosci. Jednoczesnie material w nim jest przedstawiony bardzo jasno rowniez dla poczatkujacych w branzy.

  2. Marcin says:

    DateFormat dateFormat = new SimpleDateFormat();
    Date date = new Date(2019, 1, 10);
    String formattedDate = dateFormat.format(date);

    formattedDate = 10 stycznia 2019
    ciekawe czy trafiłem?
    🙂

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *