Z biznesowego punktu widzenia tym, czego oczekuje klient, jest działająca aplikacja. Nic więc dziwnego, że dla niego testy jednostkowe są zwykle jedynie dodatkiem, z którego najchętniej by zrezygnował. I rzeczywiście – do niedawna nie przykładano tak wielkiej uwagi do pisania testów jednostkowych i w takiej konwencji powstało co najmniej kilka poważnych aplikacji, o których wiem, a wśród nich programy do gospodarowania magazynami, wspierające działanie sklepów, instytucji finansowych i banków. Jednak na przestrzeni ostatnich 15 lat obserwujemy na szczęście ewolucję sposobu postrzegania testów, a dawne podejście staje się powoli „prehistorią developmentu”. Przenieśmy się więc do współczesności i przyjrzyjmy stosowaniu Test-Driven Development, czyli podejściu, które jest ‘state of the art’ współczesnych testów oprogramowania.

Z tego artykułu dowiesz się:

  • Czym jest Test-Driven Development,
  • Czym może skutkować brak TDD – case study,
  • Jaki jest schemat ideowy Test-Driven Development,
  • Jak minimalizować liczbę testów,
  • Jak zadbać o czytelność testów jednostkowych,
  • Jakie są korzyści z TDD dla klienta i developera.

Test-Driven Development

Test-Driven Development to jedna z technik zwinnego wytwarzania oprogramowania. Technika TDD autorstwa Kenta Becka, jednego z autorów Agile Manifesto, polega na wielokrotnym powtarzaniu cyklu Red – Green – Refactoring, o czym piszę poniżej. Tym, co wyróżnia technikę Test-Driven Development, jest to, że programista rozpoczyna pracę od pisania testów jeszcze nienapisanej funkcji. Pozwala także na dodawanie jakiejkolwiek nowej funkcjonalności do rozwijanej aplikacji, minimalizując ryzyko pojawienia się przypadkowych błędów w niezmodyfikowanych obszarach programu, o których powiązaniach po kilku miesiącach mogliśmy zapomnieć. Jedną z zalet Test-Driven Development jest to, że modyfikując lub dodając funkcjonalność, automatycznie otrzymujemy raporty o wszystkich powiązanych obszarach, popsutych przez nasze zmiany. Daje to szansę, by szybko je poprawić i nie dopuścić do ich wdrożenia wraz z funkcjonalnością. O Test-Driven Development można by pisać dużo – w tym artykule chciałbym skupić się na stosowaniu TDD w praktyce oraz korzyściach płynących z takiego rozwiązania.

Przeczytaj także: Automatyzacja testów – obalamy mity

Brak stosowania TDD – case study

Przed laty, gdy stawiałem pierwsze kroki jako programista, trafiłem do projektu, w którym wspólnie z kolegą po fachu mieliśmy za zadanie stworzyć dedykowany konfigurator dla producenta okien. Sam fakt złożoności technologicznej produkcji okien powinien zdeterminować maksymalną przezorność. Użytkownik aplikacji mógł skonfigurować okno, bazując na parametrach zarówno standardowych, jak i niestandardowych. W zależności od wymiarów pewne opcje były dostępne lub nie. Użytkownik miał do pokonania kilka-kilkanaście kroków z wieloma możliwościami dla każdego. Na ostateczną cenę wpływały różne opcje i czynniki.

W projekcie tym zaobserwowałem co najmniej kilka nie najlepszych praktyk, np. brak kodu testującego czy code review. Manager i klient nie wiedzieli jednak, że w miarę możliwości staraliśmy się recenzować pracę, którą dostarczaliśmy, a której efekty były przeznaczone do stosowania bezpośrednio na środowisku produkcyjnym.

Przez cały czas pracy przy projekcie czułem, że pracuję pod ogromną presją. Nie polemizowałem, gdy powiedziano mi, że w języku PHP (w którym pisaliśmy), nie przeprowadza się testów jednostkowych. W efekcie kod, który po modyfikacjach rozbudowywaliśmy, wymagał wielokrotnych poprawek. Gdybyśmy wówczas stosowali Test-Driven Development, udałoby się uniknąć wielu frustracji związanych z niekończącymi się poprawkami. Jak więc działają TDD?

Schemat ideowy Test-Driven Development

Schemat ideowy Test-Driven Development wydaje się wskazywać na coś bardzo prostego. Przyjrzyjmy się jego poszczególnym elementom.

TDD schemat

Red – to nic innego jak etap pisania testów jednostkowych dla funkcjonalności, które uruchomione na aplikacji bez zaimplementowanego rozwiązania, siłą rzeczy wygenerują raport o błędach, wskazujący w najlepszym razie na niepoprawne działanie aplikacji w tym obszarze (czyli jej brak). W większości współczesnych narzędzi programistycznych do pisania kodu raport taki będzie zabarwiony charakterystycznym kolorem czerwonym. Ten etap jest bardzo istotny. Właśnie teraz developer dokonuje rozpoznania problemu, nad którego rozwiązaniem pracuje, analizując dotychczasowy kod opisujący ten obszar (jeżeli tylko taki istnieje). To również na tym etapie spoczywać będzie na jego barkach największy ciężar podczas implementacji nowej funkcjonalności.

Green – to kolejny etap, w którym czerwień z kolejnych raportów po uruchamianiu testów jednostkowych będzie w sposób progresywny przechodzić do barwy zielonej. Przejście testów będzie sygnalizowane przez IDE (Integrated Development Environment). To na tym etapie dokonywać się będzie właściwa implementacja nowej funkcjonalności.

Refactoring – na tym etapie wprowadzamy poprawki do nowo zaimplementowanych funkcjonalności. Możliwie uwspólniamy zastosowane również w innych miejscach podobne rozwiązania. Dla właściwej implementacji poprawiamy czystość napisanego kodu i przygotowanych przypadków testowych. Ta faza nie oznacza końca testów, gdyż po kolejnych modyfikacjach kodu cykl Red – Green – Refactoring powtarza się.

TDD – czy można zacząć od fazy Green?

Możemy oczywiście osiągnąć podobne rezultaty, zaczynając pracę od fazy Green, poprzez Red, aż do Refactoringu. Jako że jest całkiem spora szansa na zaimplementowanie aplikacji, zanim jeszcze przygotujemy do niej zestawy testów jednostkowych, taka ścieżka wydaje się nawet kusząca. Jest jednak haczyk w takim podejściu. W ferworze walki z kolejnymi funkcjonalnościami możemy zacząć przywiązywać coraz mniejszą uwagę do jakości naszych scenariuszy testowych. Nie twierdzę co prawda, że tak musi być, ale prędzej czy później nasz kod testujący może nie pokrywać możliwych scenariuszy lub nawet stać się bezwartościowy. Rośnie wówczas ryzyko wystąpienia poważnych błędów na środowisku produkcyjnym – błędów trudnych do naprawy i najczęściej w rzadko używanych obszarach. To często skutkuje sporymi wydatkami po stronie klienta związanymi z naprawą błędów.

TDD – zalety rozpoczynania od fazy Red

Dużo się mówi o tym, że rozpoczynając realizację funkcjonalności od przygotowania testów jednostkowych, będziemy potrzebować więcej czasu na realizację zadania niż w sytuacji, gdy prace zaczniemy od implementacji. To może być prawdą, gdy programista zaczyna pracę nad nowym projektem i wszystko musi przygotować od zera. Wówczas może pojawić się potrzeba utworzenia dodatkowych klas wspierających tworzenie przypadków testowych lub wdrożenia jednego z dostępnych template engine’ów do generowania requestów albo eventów. W nieco późniejszych okresach rozwoju aplikacji, jeżeli pojawią się jakieś różnice, to raczej na marginalnie niskim poziomie.

Jak minimalizować liczbę testów?

Czy nie powinniśmy tworzyć scenariuszy testowych dla każdej, nawet najmniejszej drobiny naszego kodu? Wiadomo, że najmniejsze nawet drobiny są składowymi pewnych większych struktur i zwykle jest szansa na to, by można je wspólnie opisać przy pomocy kilku rozsądnych scenariuszy przypadków brzegowych. Oczywiście zdarzają się takie obszary, jak na przykład walidatory czy kalkulatory, których często nie sposób zbadać i pokryć niewielką ilością przekrojowych testów. Chciałbym jednak podpowiedzieć, jak minimalizować liczbę testów, oszczędzając tym samym czas i wykorzystując na co dzień pełen potencjał TDD.

Sprawdź: Testy jako usługa

Testy a nieistniejące przypadki biznesowe

Ciekawym zagadnieniem jest pokrywanie testami nieistniejących przypadków biznesowych. Dylematu tego nie napotkamy zwykle podczas tworzenia testów dla walidatorów czy filtrów, ale podczas pisania testów z działania tworzonej funkcjonalności – już tak. Chodzi zwykle o sytuacje, które z takich czy innych powodów nie zadzieją się nigdy – jak wtedy, gdy nasza funkcjonalność zależeć będzie od danych ze ściśle określonych i zdefiniowanych zbiorów. Przykładem może być wybór kodu kraju wystąpienia szkody komunikacyjnej, gdy wykorzystujemy przeznaczoną funkcjonalność do ich rejestracji. Mając świadomość, że użytkownik będzie mógł wskazać jeden ze zdefiniowanych w bazie kodów, wiemy, że nie będzie miał on opcji wyboru innego lub nieistniejącego kodu podczas rzeczywistej pracy aplikacji. Czy w takim razie ma sens testowanie tego, jak zareaguje aplikacja, gdy użytkownik wybierze kod kraju „XXL”, jeżeli do dyspozycji zawsze będzie miał np.: tylko „PL” i „DE”?

Stosuj parametryzację

Kolejnym przykładem minimalizacji liczby testów z zachowaniem pokrycia jest parametryzacja, czyli technika konsolidowania scenariuszy, do której używania chciałbym zachęcić. Mimo że w tym podejściu nadal tworzymy tyle samo przypadków testowych, to jednak samego kodu testowego jest mniej. Z racji wielości warunków koniecznych do zbadania takie rozwiązanie sprawdzić się może podczas testowania klas walidatorów, kalkulatorów itp. Jego wadą może być spadek czytelności opisów takich testów zawartych w nazwach metod, co zwykle towarzyszyć może wszelkim aktom konsolidacyjnym. Nie będzie to jednak żadną przeszkodą ani problemem, jeśli w naszym projekcie zdecydujemy się na wykorzystanie współczesnych narzędzi, jak Spock Framework, który dostarczy nam znacznych ułatwień podczas samego pisania asercji. Ułatwienia te otrzymamy stricte z języka Groovy, na którym bazuje wspomniany framework. Język Groovy zapewnia większą przejrzystość w kontekście testów i dokumentacji. W łatwy sposób uzyskamy nazwy scenariuszy, ale też możliwość dodania dodatkowych opisów. Pozwoli to także zachować czytelną formę przypadków testowych, zgodną z „given, when, then”. Na pewno pozwoli to lepiej udokumentować nasze funkcjonalności.

Możemy niekiedy trafić na zwolenników tworzenia osobnych przypadków per scenariusz, czyli niekonsolidowania. Wówczas jednak wracamy do tematu przyszłego utrzymania wszystkich testów, gdy nasza aplikacja będzie się rozwijać. Zawsze jest coś za coś… Zwykle najrozsądniej jest jednak zachować umiar. Gdy w czasie poszukiwania złotego środka pojawi się temat rozbudowany i skomplikowany, to pomimo wielu podobieństw w przypadkach testowych moim zdaniem warto rozpisać je z osobna, jeden po drugim. Kategoryczność podejścia może doprowadzić do zagubienia użyteczności, jaką powinny wnieść testy jednostkowe do naszej codziennej pracy. Skupiając się zbytnio na formie, możemy zatracić rozumienie funkcji scenariuszy testowych.

Zadbaj o czytelność testów jednostkowych

Tak samo ważne jest, by skupiając się na funkcji samego przetestowania analizowanych obszarów, nie zatracić formy. Estetyka i opisowość w nazwach scenariuszy, użytych nazwach metod czy zmiennych dokumentuje nam obszary funkcjonalności i pozwala wykorzystywać test w sposób świadomy i zrozumiały. Zaryzykuję stwierdzeniem, że miarą użyteczności testu jednostkowego będzie zarówno rozsądne pokrycie testem badanego obszaru, jak i czytelność jego implementacji. Takie podejście uchroni nas przed sytuacją, której wielu mogło doświadczyć, a mianowicie kiedy po dłuższym czasie trudno jest zacząć pracę nad rozwojem dawno zaimplementowanego obszaru. Bez starannie napisanych, czytelnych testów nasza praca będzie prawdziwą drogą przez mękę.

Korzyści z TDD dla klienta i developera

TDD dla developera to przede wszystkim:

  • wyższy komfort pracy – również pracy z istniejącym kodem dzięki mniejszej liczbie błędów,
  • dokumentacja funkcjonalności na wysokim poziomie,
  • lepsza wiedza biznesowa, a tym samym lepsza znajomość produktu,
  • większa szansa na czysty kod,
  • łatwiejszy start pracy w projekcie
  • redukcja stresu i mniej presji w pracy,

TDD dla klienta to:

  • wyższy poziom pewności co do poprawności działania aplikacji i mniej błędów,
  • możliwość przewidywania kosztów utrzymania aplikacji oraz wprowadzania modyfikacji do już istniejących rozwiązań,
  • oszczędności na wdrażaniu nowych programistów do pracy przy projekcie,
  • szybsze lokalizowanie i naprawianie zaistniałych błędów,
  • większa szansa na interesujące sugestie od developera co do wprowadzanych rozwiązań, dzięki lepszej znajomości obecnych funkcjonalności,
  • TDD oznacza lepszy kod, a więc i lepszą aplikację.

Podsumowanie

Mam nadzieję, że udało mi się przekazać, co wnosi Test-Driven Development do codziennej pracy i jakie mogą być konsekwencje niestosowania go w praktyce. Wspomniane przeze mnie zasady sprzyjające minimalizacji testów: parametryzacja, minimalizacja ilości, pomijanie nieistniejących przypadków biznesowych w scenariuszach oraz czytelność kodu testowego nie wynikają bezpośrednio z definicji Test-Driven Developmentu. Nie sposób jednak w praktyce osiągnąć prawdziwego rozwoju za pomocą testów bez rozsądnego ich przestrzegania. Wierzę, że udało mi się zaprezentować, czym jest metodyka Test-Driven Development i jak wielką wartość niesie ze sobą bazująca na niej produkcja dedykowanego oprogramowania.

Przeczytaj także: Jak uzupełnić braki w zespole testowym

 

Autorem wpisu jest:
Marcin Jawor, JCommerce
Od ponad siedmiu lat java developer, a od ponad roku na tym stanowisku w JCommerce. Prywatnie miłośnik jazzu i piwa rzemieślniczego. Uważa, że w pracy developera najważniejsze są trzy rzeczy: dbałość o czysty kod, solidne testy jednostkowe i konsensus.

Dodaj komentarz

Komentarze:

Skontaktuj się z nami

Chcesz dowiedzieć się więcej o naszych usługach? Napisz do nas – odpowiemy na każdą wiadomość.

Niniejszym wyrażam zgodę na przetwarzanie przez JCommerce Sp. z o.o. moich danych osobowych (dalej „dane osobowe”), takich jak: imię i nazwisko, adres e-mail, nr telefonu, firma, w celach handlowych.
Niniejszym wyrażam zgodę na przetwarzanie przez JCommerce Sp. z o.o. moich danych osobowych (dalej „dane osobowe”), takich jak: imię i nazwisko, adres e-mail, nr telefonu, firma, w celach marketingowych.
Niniejszym wyrażam zgodę na przetwarzanie przez JCommerce Sp. z o.o. moich danych osobowych (dalej „dane osobowe”), takich jak: imię i nazwisko, adres e-mail, nr telefonu, firma w celach rekrutacyjnych.
Niniejszym wyrażam zgodę na przetwarzanie przez JCommerce Sp. z o.o. moich danych osobowych (dalej „dane osobowe”), takich jak: imię i nazwisko, adres e-mail, nr telefonu, firma na potrzeby przyszłych rekrutacji.
W związku z obowiązującymi przepisami dotyczącymi ochrony danych osobowych tj. Ustawą o ochronie danych osobowych z dnia 10 maja 2018 roku, jak również treścią Rozporządzenia Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych oraz uchylenia dyrektywy 95/46/WE (RODO), informujemy, że: 1. Administratorem danych osobowych jest JCommerce Sp. z o.o. z siedzibą w Katowicach, ul. Ściegiennego 3, 40-114 Katowice (KRS: 00007393418).
2. Powyższe dane osobowe przetwarzane będą przez JCommerce Sp. z o.o. – w zależności od udzielonych przez Panią/Pana zgód (podstawa prawna przetwarzania: art. 6 ust. 1 pkt a) RODO):
• w celach handlowych,
• w celach marketingowych.
3. Podanie powyższych danych osobowych nie jest wymogiem ustawowym, umownym lub warunkiem zawarcia umowy. Nie jest Pan/Pani zobowiązany/a do podania powyższych danych osobowych, jednak brak ich podania uniemożliwi realizacje ww. celu.
4. Posiada Pan/ Pani prawo dostępu do treści swoich danych, w tym otrzymania ich kopii i ich sprostowania, usunięcia, ograniczenia przetwarzania, prawo do przenoszenia danych, prawo do sprzeciwu wobec przetwarzania, prawo do cofnięcia zgody w dowolnym momencie, jeśli została udzielona. Wycofanie zgody nie wpływa jednak na zgodność z prawem przetwarzania, którego dokonano na podstawie zgody przed jej wycofaniem; oświadczenie o cofnięciu zgody na przetwarzanie danych osobowych należy złożyć w siedzibie JCommerce Sp. z o.o. lub przesłać na adres mailowy zgody@jcommerce.pl. Cofnięcie zgody na przetwarzanie danych osobowych skutkuje brakiem możliwości realizacji ww. celów przetwarzania;
5. Dane osobowe są udostępniane przez JCommerce Sp. z o.o. upoważnionym pracownikom i osobom współpracującym z JCommerce Sp. z o.o. na podstawie umów cywilnoprawnych, przez których realizowany jest cel przetwarzania;
6. Wszelkie pytania dotyczące ochrony danych osobowych oraz realizacje przysługujących praw, prosimy kierować na adres odo@jcommerce.pl;
7. W zależności od udzielonej zgody, dane osobowe będą przetwarzane przez czas niezbędny do realizacji ww. celów przetwarzania. W przypadku wniesienia sprzeciwu, JCommerce Sp. z o.o. przestanie przetwarzać Pani/Pana dane w ww. celu, chyba że będzie w stanie wykazać, że w stosunku do tych danych istnieją ważne prawnie uzasadnione podstawy, które są nadrzędne wobec Pana/Pani interesów, praw i wolności, lub niezbędne do ewentualnego ustalenia, dochodzenia lub obrony roszczeń;
8. Nie przekazujemy Pani/Pana danych poza teren Europejskiego Obszaru Gospodarczego oraz do organizacji międzynarodowych.
9. Pani/Pana dane osobowe nie podlegają zautomatyzowanemu podejmowaniu decyzji, w tym profilowaniu.
10. Ma Pani/Pan prawo wniesienia skargi do organu nadzorczego gdy uzna Pan/Pani, iż przetwarzanie ww. danych osobowych narusza przepisy ogólnego rozporządzenia o ochronie danych osobowych z dnia 27 kwietnia 2016 r.