Programiści programują, testerzy testują. Dlaczego programiści nie chcą pisać testów? 

Programiści programują, testerzy testują – według wielu osób w branży tak powinien wyglądać podział obowiązków w projekcie. Rozdzielanie grubą kreską obowiązków na każdym stanowisku nie ma większego sensu. Programujący testerzy i testujący programiści to nie moda czy fanaberia, ale potrzeba. Potrzeba rynku, zespołów i jakości samych projektów. Ciągle jednak znajdują się tacy, którzy najbardziej na świecie chcą utrzymać status quo i… swoje stanowiska. Należą do nich również programiści, którzy nie piszą testów.

Dbanie o jakość

Z jakiegoś niezrozumiałego powodu wielu programistów uważa, że dbanie o jakość to sprawa wyłącznie tych osób, które słowo ‚quality” mają w nazwie swojego stanowiska. Nie jest to jednak takie proste. W podejściu waterfall kaskadowo następowały po sobie kolejne fazy rozwoju oprogramowania. Testowanie było po programowaniu i najczęściej zajmował się tym zadaniem inny zespół. Nie bez przyczyny to podejście wychodzi z powszechnego użytku: ciężko i drogo jest wyłapywać błędy wyłącznie w końcowych fazach projektu. Klasyczne procesy są wypierane przez praktykę shift left, gdzie dbanie o jakość jest przesuwane coraz bardziej ‚w lewo”, czyli najbliżej początku projektu, jak to tylko możliwe.

Robert Pressman, autor książki „Software Engineering: A Practitioner’s Approach”, mówi, że błąd wychwycony na etapie implementacji jest 10 razy tańszy niż po tym, jak trafi na produkcję. Powinniśmy więc zrobić wszystko, żeby wadliwy kod został odpowiednio wcześnie zweryfikowany. Jeśli tym zadaniem zajmie się tylko jedna osoba, która testuje gotową funkcjonalność, może to się okazać za mało. Warto dbać o jakość oprogramowania na każdym etapie jego wytwarzania – programowanie nie jest wyjątkiem. Dobieranie testów odpowiednich do potrzeby każe się programistom zastanowić, co w ogóle chcą zrobić i czy wykonane zadanie jest zgodne z wymaganiami.

Najważniejszy podział testów dotyczy ich poziomów. Wyróżniamy testy jednostkowe, integracyjne i systemowe (nazywane też end-to-end). Testy jednostkowe są najbliżej kodu i sprawdzają jego działania w izolacji. Taki poziom testów najlepiej sprawdzi się w przypadku logiki biznesowej. Dla połączeń między serwisami oraz komunikacji z zewnętrznym API czy bazą danych lepsze będą testy integracyjne. Takie testy sprawdzają komunikację między różnymi elementami systemu. Żeby sprawdzić, czy aplikacja w ogóle ‚wstaje” i czy podstawowe scenariusze działają, niezbędne będą testy systemowe, będące testami na żywej aplikacji. Takie testy powodzeniem zastąpią wiele testów manualnych. Możliwe, że niektóre funkcjonalności będą potrzebować tylko niektórych rodzajów testów. Żeby jednak to ocenić, potrzebne jest doświadczenie w pracy z testami. Niektórzy programiści w ogóle nie chcą ich pisać. Podczas moich prezentacji, kursów i szkoleń często pytam ich o to, dlaczego.

Niedojrzałość społeczności

Jednym z powodów, dla którego programiści nie chcą pisać testów, jest słabe wsparcie w społeczności. Sama wywodzę się ze świata JVM (Java Virtual Machine; to platforma, dla której pisze się programy w językach takich, jak Java, Scala czy Kotlin), gdzie pisanie testów jednostkowych i integracyjnych jest raczej normą. To wynika nie tylko z obecności dobrych narzędzi dla języków typu Java czy Scala, ale też dobrych praktyk, promowanych przez społeczność programistów. Większość dojrzałych technologii ma szereg bibliotek i frameworków, które pomagają programistom pisać dobre testy. Są jednak języki, gdzie dobre praktyki dopiero powstają, a całkiem niedawno nie było prawie żadnego wsparcia dla testów.

Jednym z obszarów, w którym ciągle brakuje dobrych praktyk, jest front-end. Na szczęście dużo się w tym temacie zmienia, głównie dzięki narzędziom do efektywnego pisania testów (takich, jak Jest czy Cypress). Temat testów jest też coraz częściej poruszany na front-endowych konferencjach. Niestety, programiści, którzy zaczynali kilka lat temu, mogli przejść przez wiele projektów, nie pisząc choćby jednego testu. Podobnie sprawa ma się w aplikacjach mobilnych, gdzie oddzielenie logiki biznesowej od szczegółów konfiguracji bywa bardzo trudne. Programiści tych technologii często mówią o niedojrzałości narzędzi i braku dobrych praktyk w społeczności. Skoro nie ma wsparcia, to ciężko jest być pionierem i uczyć innych. Konsekwencją obojętności danej społeczności jest powstawanie kolejnych projektów z długiem technologicznym. Skoro powstało już tyle projektów bez testów, to dlaczego w kolejnym testy miałyby nam być do czegokolwiek potrzebne?

Ciekawym przypadkiem jest społeczność Ruby. Wiele osób, które do niej dołączyły, przeniosło się ze świata Javy i zabrało ze sobą dobre praktyki w temacie testów. Zarówno świadomość, jak i kompetencje społeczności są zdumiewające. Wiele razy słyszałam od programistów tego języka, że w Ruby wszyscy piszą testy i ciężko wyobrazić sobie inną rzeczywistość w projekcie. Jest to też temat często poruszany na konferencjach tej społeczności, w artykułach i kursach. To najlepszy dowód na to, że wsparcie społeczności wywiera pozytywną presję. Jest się też od kogo uczyć i kogo zapytać w razie trudności, które pojawiają się zwłaszcza na początku.

Biznes nie pozwala

Programiści często twierdzą, że ludzie z „biznesu” nie pozwalają im pisać testów. Kiedy posłuchamy argumentów typu testy są za drogie i biznes nie chce za nie płacić albo testy nas spowalniają, a musimy szybko dostarczać, to brzmią nawet logicznie. Dopóki nie zaczniemy drążyć tematu. Często okazuje się, że to sami programiści wyciągnęli wnioski, że trzeba odrzucić testy, bo zajmują sporą część czasu przeznaczonego na wykonywanie zadań. Podrążmy głębiej. Dlaczego testy zajmują tyle czasu? Jeśli umiemy pisać dobre testy, to powinny nam ten czas oszczędzać. Dodanie testów do kodu zajmuje niewiele czasu, jeśli wiemy, co i jak przetestować. Może chodzi o brak umiejętności? A może o niechęć do pisania testów? A może zrzucenie odpowiedzialności na testerów? Być może wszystkiego po trochę.

Biznes broniący pisania testów jest – z mojej perspektywy – trochę jak Yeti: wiele osób o tym mówi, ale kiedy kilkakrotnie zapytałam moją społeczność, kto miał taki przypadek, że klient powiedział wprost i bez przyczyny, że nie chce w tym projekcie testów, to nie znaleźli się śmiałkowie, którzy chcieliby opowiedzieć taką historię. Wyjątkiem są oczywiście projekty MVP (z ang. minimum viable product), gdzie chodzi o przygotowanie szkicu rozwiązania i tak naprawdę nie wiadomo co stanie się z tym kodem późnej. Chociaż znam też takie osoby, które są tak przyzwyczajone do stosowania techniki tworzenia oprogramowania, w której najpierw piszemy test, a później kod – TDD (z ang. test-driven development) i zawsze zaczynają od testów, żeby lepiej zrozumieć temat.

W branży IT często odnosimy się do porównań z mechanikami samochodowymi czy chirurgami – wtrącanie się klientów w ich pracę jest co najmniej nie na miejscu. Sami jednak pozwalamy, żeby ktoś mówił nam, jak mamy pracować, zamiast zaufać naszej ekspertyzie. Może to wynikać z braku przekonujących argumentów po naszej stronie. Możemy po prostu nie wiedzieć, co testy nam dadzą i od jakich problemów uchronią. Jeśli nie piszemy dobrych testów, to skąd mamy wiedzieć? Brak zaufania może mieć też związek z dotychczasowymi doświadczeniami. Jeśli nie zapracowaliśmy sobie na zaufanie, to nic dziwnego, że nie jesteśmy traktowani jak eksperci.

Może po prostu nie umiemy?

Brak umiejętności jest chyba najczęstszą przyczyną niepisania testów. Niestety, nie przychodzi ono naturalnie razem z rozwijaniem rzemiosła programistycznego.

Często obserwuję takie sytuacje, kiedy programista z wieloletnim doświadczeniem nie umie pisać testów nawet w połowie tak dobrze, jak junior z kilkumiesięcznym stażem. Wynika to z tego, że ów junior szlifował równocześnie i kod i testy, a ten doświadczony nie miał szczęścia do projektów. Mógł też sam uznać, że testy to zbędny luksus i żyć w tym przekonaniu przez wiele lat.

Im dłuższy jest nasz staż, tym trudniej przyznać się do niewiedzy i braku umiejętności w różnych dziedzinach. Dlatego doświadczeni programiści często bronią tezy, że testy są bezużyteczne, bo tak jest po prostu łatwiej. Są też tacy, którzy próbowali pisać testy, ale nie wyszło to dobrze i faktycznie projekt na tym ucierpiał. Powodem nie jest jednak samo narzędzie testów, ale nieumiejętność posługiwania się nim. Złe testy potrafią skutecznie zabetonować nasz kod i uodpornić go na zmiany w przyszłości. Każdy musi przejść ten etap samodzielnie, jednak dla osób, które świadomie rozwijają tę umiejętność, ten etap będzie bardzo krótki.

Proces nauki wymaga od nas cierpliwości i akceptacji porażek. Wiele osób ma z tym problem w naszej branży. Często uważamy, że z poziomem doświadczenia powinna przychodzić lekkość nauki nowych rzeczy, jednak nie zawsze tak jest. Łatwiej nam nauczyć się obsługi kolejnej biblioteki niż nowej umiejętności, która wymaga zmiany podejścia. Wiele osób przy pierwszych problemach się poddaje. Dobrze, że nie mieliśmy tak słabego zapału, kiedy uczyliśmy się chodzić. Inaczej do dzisiaj raczkowalibyśmy po świecie.

Dlaczego warto pisać testy?

Zazwyczaj przyczyną zmian jest jakiś problem. Kiedy czujemy, że coś jest nie tak, szukamy możliwości poprawienia tej sytuacji. Testy zazwyczaj pojawiają się jako odpowiedź na zbyt dużą liczbę testów manualnych, liczbę godzin spędzonych na wykrywaniu przyczyny usterek albo naprawianiu błędów występujących na produkcji. Jeśli nie doświadczymy tych problemów, możemy nie docenić mocy testów.

Oprócz ratowania nas przed wpadkami i oszczędzania czasu, testy mają pozytywny wpływ na kształt samego kodu. Kod, który jest pisany z myślą o testach albo nawet po testach (jak w przypadku wspomnianej techniki TDD), jest lepiej podzielony. To zwiększa jego czytelność, zatem oszczędza czas potrzebny na jego zrozumienie. Pamiętajmy, że w codziennej pracy więcej czasu spędzamy właśnie na czytaniu kodu, nie na jego tworzeniu.

Testy są też świetną dokumentacją, która się nie przedawnia. Zawsze dokładnie odzwierciedlają zaprogramowane funkcjonalności. Wprowadzanie zmian do modułów, które mają testy, jest dużo łatwiejsze nie tylko ze względu na zabezpieczenie przed regresją – z testów można dużo szybciej dowiedzieć się jak działa kod niż czytając go bezpośrednio. Testy są znacznie bardziej opisowe i więcej jest w nich naturalnego języka. O ile przyswojenie prostego kawałka kodu może zająć kilka minut, o tyle zapoznanie się z zaniedbanymi i rozrośniętym wycinkiem funkcjonalność może się okazać zadaniem na cały dzień. Jeśli istnieją testy, można od razu zapoznać się z opisami i użytymi przykładami.

Jakie testy pisać?

Testy w automatyczny sposób sprawdzają, czy oprogramowanie działa poprawnie. Jeśli logika biznesowa (serce naszej aplikacji) jest pokryta szczegółowymi testami jednostkowymi, relacje między serwisami – testami integracyjnymi, a najważniejsze ścieżki w interfejsie użytkownika – testami systemowymi, szanse na wprowadzenie szkodliwej zmiany (regresji) są małe. Dlatego żaden rodzaj testów nie powinien zostać pominięty. Mówi się, że w większości projektów występuje tzw. piramida testów, w której najwięcej jest testów jednostkowych, trochę mniej integracyjnych i niewiele systemowych (nazywanych też end-to-end).

To się jednak zmienia w trakcie cyklu życia projektu. Przykładowo: mikroserwisy prawdopodobnie będą mieć najwięcej testów integracyjnych, a odziedziczony problematyczny kod, tzw. legacy, gdzie planowana jest refaktoryzacja, będzie mieć więcej testów systemowych i integracyjnych niż jednostkowych. Inne podejście stosowane w aplikacjach nazywane jest trofeum. Zostało przedstawione przez Kenta C. Doddsa, którego działalność dotyczy głównie JavaScriptu. Dodds stwierdził, że założenia piramidy nie są już prawdziwe, a pisanie testów wygląda inaczej niż kilka lat temu. Trofeum zakłada skupienie się na testach integracyjnych.

Bez względu na to, jakich testów będzie w naszym projekcie najwięcej, warto zawsze zastanawiać się, co tak naprawdę chcemy przetestować. Wrócenie do wymagań biznesowych jest zawsze dobrym pomysłem, ponieważ sprawia, że testy przestają być dokładnym odzwierciedleniem kodu, a stają się jego realnym zabezpieczeniem. Nie chodzi nam przecież o to, żeby zabezpieczać oprogramowanie przed jakąkolwiek zmianą. W takiej sytuacji testy trzeba zmieniać za każdym razem, kiedy zmieniamy kod. Nazywamy ten proces betonowaniem. Testy mają nas raczej chronić przed niechcianymi zmianami, czyli regresją. Czy wszyscy programiści będą pisać testy?

Świadomość dotycząca pisania testów ciągle się zwiększa. Wiedza na temat dobrych praktyk jest coraz łatwiej dostępna we wszystkich technologiach. Z pewnością przybędzie osób, które nie wyobrażają sobie programowania bez testów. Czy dojdziemy do 100%? Raczej nie. To trochę taka sytuacja, jak przy liczeniu code coverage, czyli procentowej miary pokrycia kodu testami. Nawet, jeśli dążymy do pełnego pokrycia, to dużo zależy od zespołu. Sztywno ustalone normy mogą powodować pisanie słabych testów tylko po to, żeby KPI (kluczowe wskaźniki efektywności) się zgadzały. Jeśli zmusimy wszystkich do pisania testów, może być podobnie. Zawsze będzie jakiś procent programistów, którzy będą się buntować i pisać testy bez przekonania. Ponieważ walczymy tutaj o jakość, nie o ilość, starałabym się raczej pokazywać dobre przykłady, niż kogokolwiek zmuszać.