Nieodłączną częścią programowania jest testowanie. Wszyscy (oby!) programiści po napisaniu kodu testują swój program w poszukiwaniu błędów. Kiedy znajdą błąd, poprawiają go i ponownie testują w poszukiwaniu kolejnego. Przy okazji dobrze by było sprawdzić czy kolejne poprawki nie popsuły wcześniejszych. Cały ten proces jest bardzo czasochłonny i mozolny. Wielokrotnie musimy testować te same elementy. Powoduje to nudę i zniechęcenie. Dodatkowo potrafi być bardzo demotywujący i frustrujący kiedy kolejne poprawki generują nowe błędy. Skłania to często programistów do „odhaczania” testów jako zło konieczne. Jednak nie musi tak być. Część testów możemy zautomatyzować dzięki czemu oszczędzimy sobie czasu i niepotrzebnych nerwów. Tu z pomocą przychodzi nam ABAP Unit Framework. Jak sama nazwa wskazuje, służy on do wykonywania testów jednostkowych.
Czym są więc testy jednostkowe?
Jest to metoda testowania oprogramowania polegająca na testowaniu najmniejszych elementów naszego programu. Zazwyczaj są to klasy, a dokładniej ich metody. Testowany element poddawany jest testowi, który sprawdza wynik zwracany przez element z oczekiwanym przez programistę. Przy czym testy te powinny być zautomatyzowane i powtarzalne.
Trochę praktyki
W celu przetestowania wybranej klasy edytujemy ją w transakcji SE24 lub SE80. W naszym przypadku jest to klasa przykładowa ZTM_DATE_UTILS z 4 metodami. Każda z tych metod nie jest jeszcze zaimplementowana. Z górnego menu wybieramy „Local Test Classes”.
Powinniśmy zobaczyć ekran, gdzie możemy zacząć pisać nasz pierwszy test. W tym celu musimy stworzyć naszą klasę testującą. Dobrze przyjętym zwyczajem jest by klasa zaczynała się od przedrostka LTC (Local Test Class), a następnie nazwy klasy, którą testujemy. Skoro nasza klasa, którą testujemy nazywa się ZTM_DATE_UTILS to nasza klasa testowa będzie miała nazwę LTC_DATE_UTILS.
Dodatkowo do definicji klasy musimy dodać kilka słów kluczowych:
- Najważniejszym z nich jest FOR TESTING. Bez tego ABAP Unit Framework nie będzie uruchamiał testów zawartych w naszej klasie.
- Kolejnym jest DURATION + SHORT/MEDIUM/LONG. Określa on czas wykonywania naszych testów. Konfiguracja systemu, w którym wykonujemy testy określa dokładną definicję czasu wykonania i reakcję na przekroczenie tego czasu (przerwanie testu, test zakończony niepowodzeniem, brak reakcji itp.)
- Ostatnim RISK LEVEL + HARMLESS/DANGEROUS/CRITICAL. Określa on poziom ryzyka akceptowalnego przez system. Jeżeli nasz test ma wyższy poziom ryzyka niż akceptowany przez system to taki test nie zostanie wykonany.
CLASS ltc_date_utils DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
ENDCLASS.
Generator
Dla tych, którzy nie lubią ręcznie pisać kodu istniej możliwość skorzystania z generatora.
Jednak kod wygenerowany przez generator pozostawia wiele do życzenia. Co prawda kompiluje się bez problemu, jednak wymaga wielu poprawek jakościowych, które potrafią zająć więcej czasu niż stworzenie kodu od podstaw.
Pierwszy test
Kiedy mamy już definicję to możemy przystąpić do pisania pierwszego testu. Każdy pojedynczy test w naszej klasie to jedna metoda. Przy czym metoda ta musi być prywatna i zawierać w definicji słowa kluczowe FOR TESTING. Metody testowe nie mogą przyjmować, ani zwracać żadnych parametrów. Mogą jednak rzucać wyjątki.
Zdefiniujmy zatem pierwszy test, w którym przetestujemy metodę GET_FIRST_DAY_OF_MONTH. Chcemy otrzymać pierwszy dzień maja więc nazwijmy nasz test GET_FIRST_DAY_OF_MAY.
CLASS ltc_date_utils DEFINITION FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS:
get_first_day_of_may FOR TESTING
.
ENDCLASS.
Następnie dodajmy jego implementację.
METHOD get_first_day_of_may.
"Given
DATA(lo_cut) = NEW ztm_date_utils( ).
"When
DATA(lv_first_day_of_may) = lo_cut->get_first_day_of_month( 5 ).
"Then
cl_abap_unit_assert=>assert_equals(
act = lv_first_day_of_may
exp = '20190501'
msg = 'Wrong first day of may'
).
ENDMETHOD.
Omówmy sobie implementację naszego testu. W pierwszej kolejności tworzymy instancję klasy, którą chcemy testować (ZTM_DATE_UTILS) pod nazwą lo_cut. Nazwa ta nie jest przypadkowa. Przyjęte jest, że instancje klas, które testujemy nazywamy CUT (Class Under Test). Następnie wywołujemy metodę, którą chcemy przetestować (GET_FIRST_DAY_OF_MONTH), a wynik jej działania zapiszemy pod zmienną lv_first_day_of_may. Na samym końcu sprawdzamy czy faktycznie otrzymany wynik to pierwszy maja.
W tym miejscu poświęćmy chwilę uwagi na samą strukturę testu oraz na sprawdzaniu wyników.
Struktura
Możemy wyodrębnić trzy zasadnicze bloki w teście, a mianowicie Given, When oraz Then. Czym są zatem te bloki:
- Given – tutaj definiujemy stan początkowy dla testu oraz wstępne założenia
- When – W tej sekcji wykonujemy wszystkie akcje, które stanowią test. Zazwyczaj powinno być to wykonanie metody, którą testujemy
- Then – Sprawdzamy wyniki testu czyli czy nasza metoda zachowała się tak jak tego oczekiwaliśmy
Struktura Given-When-Then jest podejściem standardowym w przypadku testów jednostkowych. Nie jest obowiązkowa, jednak zdecydowanie ułatwia późniejsze czytanie i analizę testów.
Sprawdzanie wyników testu
W tym celu stosujemy standardową klasę ABAP czyli CL_ABAP_UNIT_ASSERT. Klasa ta zawiera wiele metod, które pomagają w sprawdzaniu stanu testu. Możemy dzięki niej sprawdzić czy tabela wyników zawiera konkretny rekord, czy zmienna referencyjna wskazuje na instancję obiketu, czy pole systemowe SY-SUBRC zawiera konkretną wartość itp. Zachęcam do samodzielnego sprawdzenia metod tej klasy oraz ich parametrów. W pierwszym teście zastosowaliśmy metodę ASSERT_EQUALS, która przyjmuje dwa obowiązkowe parametry czyli act (aktualnie sprawdzana przez nas wartość), exp (oczekiwana wartość) oraz dodatkowy parametr msg (treść wiadomości jeżeli test nie zostanie spełniony).
Uruchamianie testów
Skoro mamy już pierwszy test to pora go uruchomić. W tym celu użyjemy skrótu klawiszowego Ctrl+Shift+F10 (musimy ciągle znajdować się klasie, którą testujemy!). Po użyciu skrótu naszym oczom powinien ukazać się ekran:
Ekran ten składa się z 3 pod ekranów, które omówimy:
- Lista wszystkich testowanych klas wraz testami jakie się w nich znajdują (można na raz uruchomić więcej niż jeden test)
- Typ wiadomości oraz jej treść. W naszym przypadku znajdziemy tam tekst podany w parametrze msg
- Spodziewaną i aktualną wartość jakie były sprawdzane oraz stack trace naszego testu
Dlaczego nas test nie przeszedł? Otóż nie stworzyliśmy implementacji metody GET_FIRST_DAY_OF_MONTH. Dodajmy zatem bardzo prostą implementację
METHOD get_first_day_of_month.
IF iv_month_number < 10.
rv_first_month_day = sy-datum(4) && '0' && iv_month_number && '01'.
ELSE.
rv_first_month_day = sy-datum(4) && iv_month_number && '01'.
ENDIF.
ENDMETHOD.
Nie jest ona idealna, ale na nasze potrzeby w zupełności wystarczy. Ponownie stosujemy skrót klawiszowy Ctrl+Shift+F10. Powinniśmy teraz zobaczyć krótką informację na pasku wiadomości o pomyślnym działaniu testu. Co jeśli chcielibyśmy mimo wszystko zobaczyć wynik testu w tej samej formie jak w przypadku niepowodzenia? Nic prostszego. W tym celu musimy skorzystać z menu kontekstowego :
Podsumowanie
Udało nam się stworzyć test dla metody GET_FIRST_DAY_OF_MONTH. W naszej klasie pozostały jeszcze 3 metody bez testów. Sugeruję w ramach ćwiczeń samemu napisać test dla nich. Ponieważ nasz test jest zapisany, to możemy go wykonywać wielokrotnie. Jeżeli zdarzy nam się z jakiegoś powodu zmienić metodę to kilkoma kliknięciami jesteśmy w stanie szybko sprawdzić poprawność naszych zmian. Dodatkowym plusem pisania testów jest możliwość szybkiego debugowania małych kawałków kodu. Zamiast „przeklikiwać” się przez całą aplikację, żeby sprawdzić czy nowa metoda działa, wystarczy napisać krótki test, w którym możemy bez problemu ustawić break-point. Przyspiesza to znacznie pisanie kodu.
Mam nadzieję, że choć w minimalnym stopniu udało mi się przybliżyć czym jest ABAP Unit Framework. Ponieważ temat jest bardzo obszerny, zachęcam do jego większego samodzielnego zgłębienia oraz ćwiczeń.
Zostaw komentarz