Testy jednostkowe – wprowadzenie

testy jednostkowe

Testy jednostkowe są jednym z elementów procesu wytwarzania oprogramowania. Jedni je uwielbiają, inni ich nienawidzą. Niezależnie od tego, do której grupy należysz niezmienny jest fakt, że czas zainwestowany w ich tworzenie jest wart korzyści, jakie ze sobą niosą. Wszystko super, ale jak się za to zabrać? Czym one są i jakie w ogóle są zalety ich tworzenia? Jak powinno się je pisać? Jakich narzędzi do tego używać? Na te i inne pytania postaram się odpowiedzieć w serii wpisów dotyczącej testowania aplikacji, z której pierwszym jest ten, który właśnie czytasz. Z niego dowiesz się o podstawach, tak aby ugruntować wiedzę pod przyszłe treści. Zaczynamy!

Czym w ogóle są testy?

przeglądanie danych

Na samym początku zastanówmy się czym są testy jednostkowe. Jak nazwa sugeruje ich zadaniem jest sprawdzanie, a czego? Poprawności implementacji funkcjonalności. Wiem, że może to brzmieć nieco enigmatycznie, ale bez obaw – zaraz wszystko wyjaśnię. Najprościej mówiąc testy jednostkowe to kod, który jest odpowiedzialny za sprawdzenie działania innego kodu – właściwej aplikacji. Napisane są w tym samym języku , co program, który weryfikują i zazwyczaj umieszczane są w osobnym projekcie. Ich podstawowym założeniem jest to, żeby uruchamiane były często w celu sprawdzenia stanu systemu. Co za tym idzie – muszą być małe i wykonywać się szybko. Co oznacza „jednostkowe” w nazwie? Prosta sprawa – powinny testować TYLKO jedną rzecz. Nic więcej i nic mniej. Przykład: wyobraź sobie system, który obsługuje np. zakupy online. W klasycznym przypadku użycia po wybraniu produktów, potwierdzasz zakup i płacisz. Do obsługi ostatniego kroku wykorzystuje się klasę PaymentProcessor – jej zadaniem jest sprawdzenie płatności, wykonanie jej i zwrócenie rezultatu. Będąc precyzyjnym – ta klasa wywoła poszczególne etapy, które wykonane będą przez inne klasy. Przykładowy kod umieszczam poniżej:

public class PaymentProcessor : IPaymentProcessor
{
     private readonly IPaymentValidator _paymentValidator;
     private readonly IPaymentHandler _paymentHandler;
     public PaymentProcessor(IPaymentValidator paymentValidator, IPaymentHandler paymentHandler)
     {
         _paymentValidator = paymentValidator;
         _paymentHandler = paymentHandler;
     }

     public PaymentResult ProcessPayment(PaymentContext paymentContext)
     {
         _paymentValidator.Validate();

         var result = _paymentHandler.HandlePayment(paymentContext);

         return result;
     }
}

Jak widzisz PaymentValidator zweryfikuje czy można dokonać płatności, PaymentHandler wykona żądanie do zewnętrznego API obsługujące płatności. Klasa PaymentProcessor zwróci wynik całej operacji. Jak w takim wypadku napisać testy jednostkowe? Należy skupić się na operacjach wykonywanych w obrębie jednej, publicznej metody. To, co dzieje się w innych powinny sprawdzać całkowicie odrębne testy. W skrócie – testujesz tylko to co dzieje się wewnątrz metody NAZWA METODY i nie interesuje Cię jak wygląda sytuacja w PaymentValidator i PaymentHandler.

Do czego wykorzystuje się testy?

Weryfikacja działania aplikacji

szukanie błędu

Podstawowym zadaniem testów jest weryfikacja tego, czy aplikacja działa poprawnie. Na barkach programisty spoczywa odpowiedzialność przewidzenia jak największej ilości sytuacji, w jakich testowany kod się znajdzie i opisanie ich w kolejnych testach jednostkowych. Korzystając z poprzedniego przykładu można wypisać kilka przypadków:

  • płatność zostanie wywołana w momencie, gdy nie może zostać obsłużona – np. nie ma jeszcze zamówienia
  • system pozwoli na obsługę płatności, ale wystąpi jakiś problem z API za to odpowiedzialnym
  • system pozwoli na obsługę płatności, ale API nie zaakceptuje numeru karty kredytowej, której użyjemy
  • płatność będzie obsłużona poprawnie

Wspomniałem wcześniej, że „jednostkowość” testów wymaga, aby każdy sprawdzał tylko to co dzieje się w jednej, publicznej metodzie klasy, co więcej jeden test powinien weryfikować tylko jeden scenariusz. Nawiązując do przykładu dla metody NAZWA METODY w klasie PaymentProcessor napisalibyśmy 4 testy. Podążanie za tymi zasadami pozwoli na wyłapanie błędów w swoim rozwiązaniu. W trakcie mojej kariery wiele razy zdarzyło mi się, że napisane testy wykryły błędy w funkcjonalności, której dotyczyły.

Zabezpieczenie przed regresją

Wyobraź sobie sytuację, kiedy pewien komponent w aplikacji został napisany. Przygotowano do niego testy i całość wdrożono na produkcję, gdzie sobie poprawnie działało. Po pewnym czasie, inny programista implementuje kolejną funkcjonalność. W trakcie jego zadania wprowadza drobne, według niego, zmiany w kodzie odpowiadającym za część będącą już na produkcji. W wyniku tego działająca część ma błędy i nie spełnia swojego zadania we wszystkich przypadkach. Najprościej mówiąc ma buga. Taka sytuacja to tzw. regresja – gdy wprowadzone zmiany w aplikacji, sprawiają, że poprzednie komponenty nie działają poprawnie. Jak łatwo się domyślić nie jest to zjawisko pożądane. Najskuteczniejszą metodą przeciwdziałania są testy regresyjne wykonywane przez zespół Quality Assurance (testerów), są one jednak czasochłonne i kosztowne. Obejmują swoim działaniem zakres całej aplikacji i stosuje się je raczej w przypadku dużych zmian w systemie. Inną, tańszą i szybszą alternatywą są dobre testy jednostkowe. Wracając do przykładu – w przypadku gdyby pierwsza funkcjonalność była pokryta dobrej jakości testami, po wprowadzeniu błędnych zmian zaznaczyłyby problem i zmusiły programistę do naprawienia go.

Źródło informacji o systemie

Większość programistów, szukając informacji na temat systemu, zagląda do dokumentacji technicznej. Uważam jednak, że testy ZAWSZE będą bardziej aktualne. To one muszą działać poprawnie i są zsynchronizowane z kodem, który weryfikują. W miarę wprowadzania poprawek, mało kto pamięta i chce się zajmować aktualizacją dokumentacji. Zaglądając natomiast do testów jesteśmy w stanie wywnioskować jak działa system i jakich przypadków możemy się spodziewać. Jest jeden warunek. Testy trzeba utrzymywać równolegle z kodem.

Automatyzacja procesu wdrażania aplikacji

testy w laboratorium

Im bardziej skomplikowany projekt, w którym się pracuje, tym większy nakład pracy pochłania obsługa całego procesu. Wdrożenie nowej wersji na środowiska testerskie może składać się z kilkunastu kroków – budowanie aplikacji, weryfikacja poprawności, wdrożenie, itp. W celu przyspieszenia i zautomatyzowania tych kroków powstało wiele narzędzi zapewniających tzw. continuous integration. Wykonanie testów jednostkowych jest jednym z elementów, pozwalających zweryfikować, czy kod w aktualnym stanie powinien być wdrożony. Jeśli chociaż jeden z nim nie zwróci pozytywnego wyniku – zapomnij o deployu.

Podsumowanie

Dzisiejszy artykuł otworzył wrota do całkiem sporej dziedziny wiedzy, o którą zahaczyć powinien każdy szanujący się programista. Na początek zajęliśmy się podstawami. W tym wpisie omówiliśmy czym w ogóle są testy jednostkowe, a także jakie korzyści niesie za sobą ich pisanie i utrzymywanie. Oczywiście zdaję sobie sprawę, że sama teoretyczna wiedza na ten temat nie wystarczy. Moim celem jednak było przygotowanie gruntu, pod kolejne, bardziej techniczne części. W nich, bardziej szczegółowo, omówimy podstawy testowania aplikacji. Wyczekuj kolejnego wpisu, który pojawi się już za tydzień.

Please follow and like us:

Dodaj komentarz

This site uses Akismet to reduce spam. Learn how your comment data is processed.