Poziomy testów

Dzisiaj pora na kolejny artykuł z serii Quality. Po lekturze tego artykułu powinniście znać podstawowe poziomy testów. Tworząc oprogramowanie, cały czas należy mieć z tyłu głowy, że celem developmentu jest stworzenie z wizji, gotowego produktu. Testowanie jest niezbędnym procesem podczas developmentu, testowanie jest również konieczne na etapie wdrażania i utrzymania. Testy w cyklu życia oprogramowania można podzielić na kilka różnych poziomów – na przykład testy jednostkowe czy testy systemowe. Każdy poziom testów ma swoją własną charakterystykę.

Poziomy testów są ściśle związane z fazami wytwarzania oprogramowania ale dla każdego projektu mogą one wyglądać trochę inaczej. To jakie i ile tych testów powinno być oczywiście zależy od projektu. W projektach, gdy tworzony produkt jest częścią złożonego produktu – konieczne będzie zwiększenie liczby testów integracyjnych. Nie ważne jak wiele mamy poziomów testów, każdy z nich jest różny od innego, w szczególności pod względem celów i zakresu. Można zatem powiedzieć, że poziom testów to pewna grupa zorganizowanych czynności testowych.

Najczęściej spotykamy 4 poziomy testów:
– Akceptacyjne
– Systemowe
– Integracyjne
– Jednostkowe

Testy jednostkowe

Testy jednostkowe zwane są często testami komponentów, czy też testami modułowymi. Ich głównym celem jest znalezienie błędów w implementacji danej jednostki / komponentu. Obiektami testowymi są zatem indywidualne komponenty w izolacji a podstawową dokumentacją jest szczegółowy projekt i czasami inne dokumenty tj. specyfikacja wymagań.
Nie zawsze jednak łatwo jest ustalić co tak naprawdę jest tym komponentem. Komponentem może być moduł, klasa czy też funkcja. Możliwości jest wiele. Istotną rzeczą jest określenie co rozumiemy przez słowo komponent. Ważną cechą tego typu testów jest fakt, że testujemy je w izolacji od innych elementów. Jednakże, często testowany element jest zależny od innych części oprogramowania, które czasami nawet na etapie pisania testów nie są jeszcze gotowe. Czasami testowany element jest wywoływany przez inną część aplikacji a czasem wywołuje inną część aplikacji. Radzimy sobie z tym tworząc powiedzmy, elementy zastępcze aplikacji. Rozróżniamy kilka rodzajów atrap, najczęściej spotykane to stub, spy, mock, dummy. I tutaj utnę ten temat, ponieważ jest to na tyle obszerny temat, że nadaje się na osobny artykuł.

Tego typu testy bazują na wymaganiach dla danego modułu, projekcie oraz kodzie źródłowym. Testy te nie są zwykle sformalizowane, jednakże oczywiście poziom sformalizowania zależy od projektu – im większe ryzyko tym większy nacisk jest na formalizację. Obecnie tworząc testy jednostkowe mamy do dyspozycji gotowe framewoki, które znacznie nam ułatwiają ich tworzenie czy przedstawienie wyników testów w czytelnej postaci.

Podsumowując, możemy powiedzieć, że tego rodzaju testy ułatwiają nam modyfikowanie aplikacji oraz zapewniają eliminację błędów na wczesnym etapie. Testy tego typu z reguły są (a właściwie powinny być) szybkie, odizolowane od całości i łatwe do debugowania, dają dość precyzyjną informację o stanie aplikacji.

Testy integracyjne

Kolejnym poziomem są testy integracyjne, sprawdzają czy komponenty poprawnie współdziałają ze sobą. Przykładem może być współpraca między aplikacją webową a bazą danych, ale nie tylko – przykładem może być współpraca pomiędzy dwiema klasami. Można stwierdzić, że testy integracyjne mają na celu wykrycie błędów podczas interakcji pomiędzy systemami lub jego częściami.

Często możemy spotkać się z podziałem testów integracyjnych na testy integracyjne systemów i testy integracyjne modułów. Różnią się tak naprawdę między sobą skalą. Gdy mówimy o integracji funkcji, metod, klas – mamy do czynienia z tym drugim rodzajem. Gdy mówimy o łączeniu ze sobą dużych elementów takich jak programy, całe moduły aplikacji czy systemy – mówimy tu o integracji dużej skali.

Testy interfejsów

Możemy się również spotkać z testami interfejsów (w tym testy API – Application Programming Interface) czy też testy interakcji pomiędzy sprzętem a oprogramowaniem. Testy API są to zwykle test negatywne – czyli takie, które są nastawione na testy, które mają na celu sprawdzenia odporności na błędy. Nastawione są głównie na ocenę wartości wejściowych i wyjściowych. Przykładem testów interakcji pomiędzy sprzętem a oprogramowanie może być przetestowanie komunikacji między oprogramowaniem (np. systemem operacyjnym) a telefonem komórkowym.

Obiektami testów są interfejsy, które odpowiadają za komunikację pomiędzy modułami, ale i też dane konfiguracyjne. A co to są dane konfiguracyjne – w dużym uproszczeniu są to ustawienia. Różne ustawienia systemowe mogą mieć wpływ na działanie wszystkich modułów. Z kolei bazą dla tego poziomu testów zwykle jest projekt architektury systemu oraz przepływ procesów, czasami również przypadki użycia.

Gdy wykonujemy testy integracyjne powinniśmy pamiętać, że interesuje nas “współpraca” elementów a nie działanie każdego modułu z osobna.

Pojawia się pytanie – kiedy testować integracyjnie? Tak naprawdę jak zawsze odpowiedź jest jedna – jak najwcześniej. Najlepiej testować gdy powstają kolejne moduły, a same testy przebiegają inkrementacyjnie. Dlaczego? Ponieważ gdy zaczniemy testować gdy system już jest praktycznie zintegrowany – a znajdziemy błędy (a wręcz na pewno coś znajdziemy) to znalezienie właściwej przyczyny może okazać się bardzo trudne ze względu na mnogość interakcji i interfejsów. Gdy testujemy inkrementacyjnie – dużo łatwiej jest nam znaleźć źródło problemu.

Jak w większości testów możemy zastosować pewne techniki testowania. Nie inaczej jest w przypadku testów integracyjnych. Mamy do wyboru techniki oparte na architekturze, ale i również na funkcjonalności czy też na ryzyku.

Przykład

Rozważmy to na przykładzie. Rysunek przedstawia aplikację składającą się z kilku modułów.

Moduły oznaczone na czerwono, są modułami krytycznymi dla działania aplikacji. Pole numer 4 pobiera dane z zewnętrznego źródła, a numer 7 przekazuje dane na wyjście. Pierwszy moduł wywołuje moduły 2 i 6 a np. Moduł 7 jest wywoływany przez moduł 5.

Metody integracji

Mamy dwie metody integracji – zstępująca i wstępująca. Są to podejścia przyrostowe – w pierwszym przypadku – pierwszy testowany jest moduł na górze hierarchii (a moduły niższe są mockowane). Gdy górne moduły są przetestowane – są używane do testów modułów z niższych części. Proces ten jest powtarzalny, póki nie zostaną wszystkie moduły przetestowane – nawet te leżące najniżej.Metoda wstępująca jest bardzo podobna do zstępującej – różni się tym, że zaczynamy od dołu hierarchii i idziemy w górę. Dopóki moduły leżące na szczycie hierarchii nie będą przetestowane.

Na przykładzie naszego diagramu wyglądałoby to tak: Metoda zstępująca – testujemy moduł 7 – mockując poniższe moduły – następnie testujemy tworząc moduły 4, 3, 5 mając zamockowane tylko elementy 2, 6,1. Mechanizm ten powtarzając dopóki wszystkie elementy nie zostaną przetestowane w tym moduł 1. Metoda wstępująca – testujemy moduł 1 mockując wszystkie powyższe moduły. Następnie tworzymy moduły 2 i 6 mając zamockowane pozostałe moduły tak aż przetestujemy na końcu moduł 7.

Strategia oparta na integracji funkcjonalnej polega na tym że w pierwszej kolejności tworzymy moduły odpowiedzialne za dane funkcjonalności i moduły z nimi związane. A reszta pełni rolę zaślepek. Kolejną strategią jest strategia oparta na ryzyku – gdzie w pierwszej kolejności tworzymy moduły najwyższego ryzyka. A potem dopiero potem tworzyć moduły odpowiedzialne za integrację z modułami krytycznymi a na końcu te które nie wchodzą bezpośrednio w interakcję z nimi.

Podsumowując, można powiedzieć, że są to testy pośrednie, pomocnicze, pomiędzy testami jednostkowymi a testami systemowymi. Zwykle są dobrze wyizolowane, tak jak testy jednostkowe, dosyć łatwe do debugowania, dające precyzyjną informację zwrotną. Są jednak dosyć wolne, przez co powinno ich być zdecydowanie mniej niż testów jednostkowych.

Testy systemowe

Testy systemowe przeprowadzamy, gdy elementy systemu zostały ze sobą zintegrowane. Jest to ten moment, w którym sprawdzamy funkcjonalność systemu. Na tym etapie możemy przeprowadzać testy e2e (end to end). Testy e2e są to testy przeprowadzane z punktu widzenia użytkownika, obejmują całe scenariusze testowe np. zaczynając od założenia konta, obejmujące zalogowanie się do systemu oraz wykonanie operacji by na końcu się wylogować. Dzięki tym testom jesteśmy w stanie się dowiedzieć na ile nasz stworzony system spełnia pierwotne założenia.

Podstawą testów są wymagania, przypadku użycia, specyfikacja. Obiektem testów jest system, dokumentacja i konfiguracja systemu. Na tym etapie również przeprowadzamy testy niefunkcjonalne, na przykład testy wydajności, niezawodności czy security.

Celem tego rodzaju testów jest upewnienie się ze aplikacja działa zgodnie z wymaganiami użytkownika oraz znalezienie błędów. Tego rodzaju testy zwykle są wolne i złożone ponieważ często dotyczą wielu warstw aplikacji.

Testy akceptacyjne

Zazwyczaj testy akceptacyjne są wykonywane po stronie klienta lub też przez użytkowników końcowych. Do tego rodzaju testów – testowany obszar aplikacji musi w pełni działać. Głównym celem testów akceptacyjnych nie jest znajdowanie błędów a upewnienia się że aplikacja spełnia oczekiwania klienta i użytkowników.

Oczywiście na tym etapie mogą być znajdowane błędy lub zwracane uwagi do systemu nie mniej powinniśmy mieć ustalony proces zwrotny tak by wszelkie wychwycone błędy mogły być w przyszłości naprawione. Dlatego też warto ustalić sposób postępowania z klientem w przypadku wystąpienia błędu, można zastosować na tym etapie szczegółowe logowanie akcji użytkownika oraz występujących błędów. Dzięki czemu zespół łatwiej odtworzy błąd a następnie go naprawi.

Mówiąc o testach akceptacyjnych warto wspomnieć o testach alfa i beta. Testy alfa są przeprowadzane u producenta, w środowisku testowym. Testy beta są przeprowadzane u klienta, w jego własnych lokalizacjach.

Najłatwiej wyjaśnić to na przykładzie dużych koncernów. Załóżmy, że duży portal społecznościowy wypuszcza nową funkcjonalność. Część testów akceptacyjnych jest przeprowadzanych po stronie klienta. Ale pewna grupa docelowa użytkowników dostaje możliwość przeprowadzenie testów, nowej funkcjonalności, przed wdrożeniem jej dla wszystkich. Dzięki czemu nabieramy zaufania, że aplikacja działa poprawnie, otrzymujemy często informację zwrotną od użytkowników, ocenę produktu, jak i informacje które pomogą nam ocenić czy produkt spełnił oczekiwania.

Podsumowanie

Jak widać każdy poziom testów działa na różnych obiektach i ma inny cel. Dlatego tak ważne jest ich poznanie, by potem odpowiednio zaprojektować ich rozkład do projektu. Dzięki dobrze zaprojektowanym testom możemy uzyskać bardzo wysokie zaufanie do wytworzonego produktu.