Testy jednostkowe – czemu tak często sprawiają problemy?

Unit testy od wielu lat są gorącym tematem w inżynierii oprogramowania. Z jednej strony jest to świetna praktyka. Czasem wręcz uważane są za symbol dbałości o jakość kodu w firmie. Z drugiej jednak strony, często dochodzi do wielu dziwnych zjawisk, które przekształcają unit testy w wieczny ból głowy.

Po co są unit testy?

Sama idea unit testów jest genialna. Są to małe funkcje, które automatycznie sprawdzają możliwie najmniejszą część logiki biznesowej. Zwykle mamy do czynienia z testem, który pokrywa jedną metodę z klasy zawierającej konkretną funkcjonalność.

Plusy takiej praktyki są oczywiste. Przede wszystkim, automatycznie uruchamiane testy potrafią bardzo precyzyjnie zaalarmować o potencjalnie błędnej zmianie w kodzie. Jest to bardzo pomocne, jeśli nasza zmiana dotknęła pobocznej funkcjonalności, która nie powinna być przez tą zmianę ruszana. Dobre pokrycie testami znacznie utrudnia dokonywanie modyfikacji przypadkowo psujących wielu miejsc w aplikacji.

Ciekawy też jest efekt, jaki ten typ testów wywiera na sam kod i proces dewelopmentu. Unit testy są de facto wymaganiami, które stawiamy przed kodem. W ich asercjach zawieramy, to co musi być spełnione, żeby dana metoda była uznawana za poprawną. Zmusza to deweloperów do precyzyjnego definiowania wymagań i zapobiega tworzeniom fantazji w kodzie. To zjawisko jest bezpośrednim powodem siły metodologii TDD. Jeśli nie wiesz, czym jest TDD to gorąco polecam lekturę na ten temat.

Cechy dobrego unit testu

Unit testy posiadają kanon cech, które powinny być zawsze przestrzegane. Ciężko z nimi dyskutować i cieszą się praktycznie uniwersalną akceptacją.

Izolacja

Musimy dbać o to, by testy były od siebie niezależne. Każda zależność pomiędzy nimi jest tykającą bombą. Z założenia kolejność i ilość unit testów powinna być dowolna w każdym uruchomieniu. Nie przestrzegając izolacji możemy stworzyć testy, które będą losowo padać i wstawać, co jest paskudą sytuacją.

Arrange, Act, Assert

Testy powinny składać się z trzech jednoznacznie oddzielonych części:

  • arrange – ustawiamy warunki wykonania
  • act – wykonujemy testowaną metodę
  • assert – sprawdzamy wyniki zwracane przez metodę

Taka struktura zapewnia powtarzalny styl pisania unit testów. Dzięki temu są oczywiste i proste w analizie.

Tylko jedna testowana rzecz

Unit test musi testować tylko jeden przypadek użycia. Powinno być w nim tylko jedno wywołanie testowanej metody i tylko jedno sprawdzenie warunku.

Automatyzacja

Testy jednostkowe powinny być uruchamiane jak najczęściej. Najlepszym sposobem na zapewnienie częstego uruchamiania jest wcielenie ich w każde zbudowanie aplikacji. Na szczęście popularne systemy budujące (np. Maven, Gradle) mają to w standardzie.

Wysokie pokrycie

Pokrycie testami logiki biznesowej powinno być jak największe. W innych częściach systemu unit testy są najczęściej trudne do zastosowania lub nie mają sensu. Dlatego też ogólny procent pokrycia aplikacji testami jest niemożliwy do określenia.

Prędkość

Testy jednostkowe muszą być szybkie! Jeśli są poprawnie napisane, to czas ich uruchomienia powinien wynosić maksymalnie kilka sekund.

Jednoznaczne ustalenie danych, wyników i logiki wykonania

Testy nie mogą używać losowych danych ani funkcji zawierających ciężkie do określenia wyniki. Nawet jeśli podczas tworzenia testu myślimy, że zastosowana losowość powinna być zawsze w zakresie poprawnym, to nie możemy tego przewidzieć na 100%. Takie testy mogą losowo wybuchać na różnych środowiskach, co jest sytuacją bardzo trudną do opanowania. Dane i wyniki testów zawsze powinny być hardcodowane.

Najczęstsze błędy przy unit testach

Jest wiele klasycznych patologii unit testów. Bez czujności i wiedzy odnośnie dobrych praktyk pojawienie się problemów jest kwestią czasu.

Unit testy, przechodzące w testy integracyjne

Jest to jedna z najczęstszych złych praktyk. Dochodzi do niej, kiedy stwierdzamy, że nie możemy stworzyć unit testu, jeśli nie podepniemy do niego np. testowej bazy danych lub innych systemów/bibliotek. Ten tragiczny pomysł uderza w wiele założeń unit testów. Spowalnia testy, rozszerza ich zakres i zwiększa szanse na powstanie zależności. Jest to jedna z tych praktyk, która powinna być zestrzelona jak tylko ktoś wpadnie na taki pomysł. Bazy danych i inne systemy muszą być zmockowane na potrzeby unit testów.

Brak dbania o wydajność unit testów

Szybkość testów jest bardzo często ignorowana. Zwykle deweloper nie zwraca uwagi na to, że jego nowa piękna paczka dołożyła 5 s wykonania. Wolne unit testy to gotowy przepis na problemy. Jak już wiemy testy powinny być odpalane przy każdym budowaniu. Im wolniejsze są testy tym większa szansa na to, że w końcu ktoś wpadnie na cudowny pomysł – skipowanie testów. Zamulające testy to wieczny ból nie tylko w lokalnych środowiskach, ale też w całym systemie CI. W pewnym momencie tracimy za dużo czasu budowania i testy są pomijane. Takie testy lepiej wykasować niż udawać, że są używane.

Testowanie niestabilnej funkcjonalności.

Jeśli testowana funkcjonalność się zmieni, to pociągnie to za sobą testy, które były na nią wycelowane. Jeśli mamy do czynienia z taką sytuacją zbyt często, to testy stają się czasochłonnym i mozolnym zadaniem. Pamiętajmy więc, że należy precyzyjnie i ostatecznie zdefiniować wymagania, zanim przystąpimy do pisania unit testów.

Testy sprawdzające nie ważne rzeczy

Raz byłem świadkiem pytania: “czy gettery i settery powinny być testowane”. Słysząc to od razu rozbolały mnie uszy. Przede wszystkim KISS! Każdy dodatkowy kawałek kodu to ciężar, koszt i problem. Test jednostkowy, który nie sprawdza logiki aplikacji jest bez sensu.

Musimy mieć 100% coverage!

Jest to objaw bezkrytycznego szału na pisanie unit testów. Procent pokrycia jest bardzo niedoskonałą miarą jakości testowania kodu. Bardzo niski procent zwykle jest powodem do niepokoju, bo z pewnością mamy do czynienia z programowaniem bez testów. Z drugiej strony, stawianie sobie za punkt honoru wykręcenie bardzo wysokiego procenta nie prowadzi do niczego dobrego.

Podsumowując:

Unit testy to bezsprzecznie dobra praktyka, którą mogę polecić. Trzeba jednak pamiętać o zagrożeniach i mieć się na baczności. Kwestią czasu jest to, że ktoś zacznie psuć je zgodnie ze schematami przedstawionymi powyżej. W tym momencie muszą zadziałać odpowiednie procedury kontroli jakości kodu, jak np. code review. Krytyczna też jest opieka doświadczonego architekta/lidera, który nie pozwoli na żadne odchyły w tym temacie.

Leave A Comment