Artykuły | 2 maj, 2023

Narzędzia automatyzacji testów czy dobre praktyki? Jak przyspieszyć testowanie?

W miarę postępu budowania aplikacji liczba testów automatycznych odpowiednio rośnie, a ich wykonywanie zajmuje coraz więcej czasu. Na samym początku czas ich wykonywania może być liczony w minutach, a po kilku miesiącach pracy w godzinach lub nawet dniach. W tym artykule zdradzam kilka sprawdzonych metod i dobrych praktyk testerskich, które pomogą ci zredukować czas potrzebny na wykonywanie testów.

narzędzia automatyzacji testów

Czy można testować aplikacje szybciej?

Dynamicznie rozwijająca się technologia i rosnące wymagania użytkowników sprawiają, że projektowanie wydajnych i niezawodnych aplikacji internetowych staje się coraz bardziej złożone, a zapewnienie prawidłowego działania systemu wymaga dużego wysiłku nie tylko od zespołu developerskiego, ale i testowego. Dobry zestaw testów pomaga sprostać tym wyzwaniom. Niestety, dbałość o jakość nie zawsze idzie w parze z szybkością.

Nie tylko test managerowie i project managerowie nie lubią długo czekać na wyniki testów. To kłopotliwe także dla developerów – przeciągające się testy aplikacji mogą być jednym z powodów opóźnienia rozwoju oprogramowania. Dla dobra projektu testy powinny być wykonywane tak szybko, jak to możliwe (zachowując rozsądek – mają one być również niezawodne i dawać realne informacje na temat testowanej aplikacji). Poniżej kilka sposobów na to, jak przyspieszyć wykonywanie testów bez tracenia na jakości.

 class=

Równoległe uruchamianie testów

Uruchamianie testów w sposób równoległy jest najlepszym i najbardziej efektywnym sposobem na znaczną redukcję czasu przeprowadzania testów. Domyślnie w wielu narzędziach testy odbywają się w sposób sekwencyjny (a więc w danej chwili wykonywany jest tylko jeden test, a po jego zakończeniu uruchomiony zostaje kolejny). W głównej mierze możliwość ta zależy od wykorzystywanego test runnera i opcji, które do tego celu udostępnia.

Przykładowo, w narzędziu XUnit zastosowana została koncepcja zwana test collections, która określa sposób, w jaki testy mogą być wykonane równolegle. Domyślnie każda klasa testów stanowi ich unikatową kolekcję, a testy znajdujące się w tej samej klasie nie będą wykonywały się równolegle – aby było to możliwe, należy je rozdzielić na 2 lub więcej klas. Dodatkowo narzędzie to umożliwia tworzenie niestandardowych kolekcji, gdzie użytkownik może używać specjalnych atrybutów do ich tworzenia, co ułatwia zarządzanie i grupowanie testów oraz ich odpowiednie uruchamianie, w zależności od kontekstu. Inne test runnery mogą posiadać podobne funkcjonalności. Przykładowo w narzędziu Playwright można uruchamiać testy równolegle zarówno w obrębie jednej klasy testowej, jak i w całym projekcie za pomocą parametrów konfiguracyjnych. Warto dodać, że opcja ta jest domyślnie włączona.

Jak dużo czasu można zaoszczędzić, wykorzystując to podejście? Załóżmy (bez uwzględniania wydajności maszyny, opóźnień serwera, przepustowości sieci i innych czynników mających wpływ na wykonanie testów), że posiadamy kolekcję 100 testów, a każdy z nich wykonuje się średnio przez minutę. Uruchomienie ich sekwencyjnie zajmie 1 godzinę i 40 minut. Przy uruchomieniu ich w dwóch wątkach (2 testy wykonywane jednocześnie) czas potrzebny do ich wykonania skróci się o połowę – do 50 minut. Dołączenie kolejnych wątków pozwoli na jeszcze większą redukcję czasu potrzebnego na ich wykonanie.

Potencjalne problemy

Niestety, samo uruchamianie testów w ten sposób nie stanowi złotego środka, gdzie wystarczy po prostu zmienić konfigurację i zwiększyć liczbę wątków. Należy pamiętać o potencjalnych problemach związanych z równoległym uruchamianiem testów, na przykład:

  • wyniki działania jednych testów mogą (np. poprzez zmiany w bazie danych czy zużywanie / obciążanie zasobów) wpływać na wyniki innych testów. Tego typu testy należy zidentyfikować i uruchamiać sekwencyjnie (przed lub po kolekcji testów wykonywanych równolegle). Dzięki temu zapewnimy pewną izolację wszystkim testom i w trakcie wykonywania nie będą miały na siebie wpływu,
  • wydajność komputera / serwera – należy odpowiednio dobrać maksymalną liczbę testów wykonywanych równolegle, aby uniknąć problemów z wydajnością.

Odpowiednie podejście do analizy testów i podział ich na dwie grupy (te, które mogą być uruchamiane równolegle, i te, które powinny zostać uruchamiane sekwencyjnie) oraz dobór odpowiednich parametrów pozwalających na określenie maksymalnej liczby uruchamianych testów może stanowić wyzwanie, lecz korzyści płynące ze zmiany sposobu uruchamiania testów pozwolą na znaczną redukcję czasu ich wykonywania, a co za tym idzie – nie tylko na możliwość wykorzystania dodatkowego czasu na inne aktywności, ale przede wszystkim otrzymanie szybszego feedbacku z testów i możliwość szybszej reakcji na potencjalne błędy i awarie.

Tworzenie warunków wstępnych, wykorzystując interfejsy API lub działanie bezpośrednio w bazie danych

W przypadku testowania interfejsu użytkownika często niezbędne jest odpowiednie przygotowanie konfiguracji testowanej aplikacji lub wykorzystanie pewnych danych niezbędnych do przeprowadzenia testu (najczęściej są opisane jako warunki wstępne – ang. preconditions). Tego typu akcje powinny być wykonywane z wykorzystaniem API. Jeśli to możliwe, dane można przygotowywać bezpośrednio w bazie danych. Zwiększa to szybkość wykonywania testów (bezpośrednie żądania HTTP lub modyfikacja danych w bazie są szybsze niż „wyklikiwanie” poszczególnych opcji w interfejsie) oraz ich stabilność.

Potencjalne problemy

Także tu musimy się liczyć z potencjalnymi problemami, takimi jak konieczność utrzymania zgodności z API czy bazą danych. O ile API jest proste w utrzymaniu (w większości przypadków to kwestia dodania/odjęcia pól w żądaniach), o tyle w przypadku bazy danych trzeba znać jej strukturę oraz zależności między tabelami.

Konwersja testów na testy niższych poziomów

Wykonywanie testów za pośrednictwem interfejsu użytkownika jest z reguły wolniejsze i bardziej zawodne niż testy niższego poziomu, takich jak testy API lub jednostkowe. Zgodnie z piramidą testów liczba testów UI/E2E powinna stanowić mniejszą część niż liczba testów z niższych poziomów.

 class=

Testy UI wymagają uruchomienia przeglądarki i interakcji z nią, co stanowi dodatkowy koszt czasowy i wykorzystuje więcej zasobów. Renderowanie interfejsu również zabiera czas; zdarza się, że czas oczekiwania na poprawne załadowanie się strony trwa dłużej z różnych powodów (np. obciążony serwer aplikacji lub baza danych). Czasem strona może zachowywać się nieprzewidywalnie (komponent nie zostanie załadowany poprawnie, element dynamiczny nie pojawi się itp). Może to spowodować nie tylko wydłużenie czasu trwania testów, ale też być powodem ich niestabilności.

Jeśli posiadamy wiele testów UI, należy rozważyć ich konwersję na testy niższych warstw, jeżeli jest to możliwe. Przykładowo, test dostępu do strony, która może być przeglądana jedynie przez zalogowanego użytkownika (niech będzie to panel konta użytkownika), może zostać całościowo przeprowadzony na warstwie UI – włączając logowanie. Można jednak odpowiednio go zmodyfikować i zaimplementować logowanie do aplikacji poprzez API, dzięki czemu warstwa UI zostanie wykorzystana jedynie w celu weryfikacji konkretnej strony. Przeniesienie logowania „niżej” przyspieszy wykonywanie testu, ponieważ jego właściwe wykonanie po stronie UI będzie można rozpocząć z już zalogowanym użytkownikiem i wejściem bezpośrednio na żądaną stronę.

Rozbicie testów na mniejsze pakiety oraz grupowanie

Grupowanie testów samo w sobie nie wpływa na wykonywanie już stworzonych testów. Jego zadaniem jest umożliwienie selekcji konkretnych testów lub zestawów testów ze względu na ich właściwości (testowany obszar, funkcjonalność, typ, priorytet itd.) Dzięki odpowiedniemu oznaczeniu przypadków testowych otrzymujemy możliwość uruchamiania tylko tych testów, które są w danej chwili potrzebne. Główny podział może zostać dokonany przez wyodrębnienie testów stabilności, dymnych i regresji, a opatrzenie testów dodatkowymi atrybutami pozwoli na praktycznie dowolną konfigurację ich uruchamiania (np. ze względu na wspomniany już obszar i typ testów).

Nie zawsze istnieje konieczność uruchamiania całego zbioru testów. Często chcemy przetestować jedynie wybrany fragment systemu lub konkretną funkcjonalność. Ręczne ich poszukiwanie i uruchamianie byłoby czynnością bardzo czasochłonną, a gdy posiadamy duży zestaw testów, istnieje prawdopodobieństwo, że niektóre mogą zostać pominięte. Wykorzystanie atrybutów w celu filtracji pozwala na konkretną selekcję wszystkich testów, które są potrzebne w danej sytuacji. Dla przykładu – posiadając sklep internetowy i pracując nad nowym typem produktu, można uruchomić tylko testy związane z dodawaniem produktu do koszyka lub listy życzeń, zanim zdecydujemy się na pełną regresję.

Posłużę się przykładem wziętym z życia. Pracując dla klienta z branży bukmacherskiej, w trakcie trwania każdego release’u, uruchamialiśmy między innymi testy regresji.

Redukcja liczby podobnych testów

Zdarza się, że zbiór przypadków testowych składa się z testów sprawdzających praktycznie identyczną funkcjonalność, lecz wykorzystujących różne dane (np. dodanie 1 produktu do koszyka, dodanie 5 produktów do koszyka, dodanie produktu z kategorii „AGD” do koszyka, dodanie produktu z kategorii „Konsole” do koszyka). Jeżeli drugi przypadek testowy nie weryfikuje dodatkowych kryteriów (np. dodaj 5% zniżki, gdy klient posiada co najmniej 5 produktów w koszyku), należy rozważyć, czy dany przypadek testowy powinien być nadal wykorzystywany. Zdarza się to szczególnie przy dużej liczbie przypadków testowych tworzonych dla dużych systemów, gdzie w miarę rozbudowy można stracić rachubę i stworzyć przypadek testowy, który na dobrą sprawę już istnieje.. Okresowa analiza przypadków testowych i ich aktualizacja oraz weryfikacja pomoże zidentyfikować tego typu przypadki testowe, a dalsze ich badanie pozwoli odpowiedzieć na pytanie, czy analizowany przypadek testowy zostawić, zmodyfikować lub całkowicie usunąć.

Zmiana struktury testów – dopasowanie testu do testowanej funkcjonalności

Testy powinny być nie tylko stabilne i szybkie, ale też odpowiednio napisane – tak, aby testowały konkretny obszar aplikacji lub funkcjonalność. W odpowiednio zaprojektowanym teście liczba kroków potrzebnych do pełnego sprawdzenia funkcjonalności powinna być jak najmniejsza – aby nie weryfikować czegoś, co nie powinno znajdować się w obszarze testu. Przykład – testując dodawanie produktu do koszyka (zakładając, że użytkownik musi być zalogowany), nie powinniśmy skupiać się na asercji, czy użytkownik faktycznie został zalogowany – to powinno zostać sprawdzone w teście dotyczącym obszaru logowania klienta do sklepu. W taką pułapkę można wpaść w szczególności, tworząc testy w procesie BDD (przykład: specflow), gdzie często kroki testu są po prostu kopiowane z już istniejącego i umieszczane w nowym, bez odpowiedniej ich edycji w celu jak najlepszego dopasowania ich do kontekstu.

Rozważmy test logowania użytkownika do aplikacji:

Scenario: Log in to application 
Given I navigate to the homepage 
And I click Login button 
When I log in as ‘testUser’ with password ‘testPassword’ 
Then I assert that My Account button is displayed 
And ‘testUser’ name is displayed 

Można wykorzystać jego kroki i dodać je na początku testu dodawania produktu do koszyka:

Scenario: Add product to basket 
Given I navigate to the homepage 
And I click Login button 
When I log in as ‘testUser’ with password ‘testPassword’ 
Then I assert that My Account button is displayed 
And ‘testUser’ name is displayed 
When I navigate to a test product page 
And I add product to basket 
Then Basket should contain test product 

Kroki dotyczące poprawności zalogowania użytkownika nie są istotne w kontekście dodawania produktu do koszyka – co więcej, test logowania jest zduplikowany. Jego sens stoi pod znakiem zapytania, ponieważ logowanie będzie sprawdzane w każdym teście, który zostanie napisany na jego podstawie.

Zamiast tego powinien zostać on odpowiednio zmodyfikowany, aby uwzględniać tylko funkcję dodawania do koszyka:

Scenario: Add product to basket 
Given I navigate to a test product page 
When I log in as ‘testUser’ with password ‘testPassword’ 
And I add product to basket 
Then Basket should contain test product 

Dzięki temu test został zoptymalizowany pod kątem szybkości i testowanej funkcjonalności.

Nawiązując do aspektów związanych z tworzeniem warunków wstępnych, logowanie można przenieść do tej sekcji i dokonać go po stronie API, dzięki czemu test mógłby rozpocząć się bezpośrednio od nawigacji na stronę produktu, gdzie użytkownik byłby już zalogowany:

Background: User is Logged In 
Given I login as test user through API 
Scenario: Add product to basket 
Given I navigate to a test product page  
When I add product to basket  
Then Basket should contain test product 

Idąc o krok dalej – nawiązując do konwersji testów, jeśli nie jest konieczne sprawdzenie czegoś po stronie UI, można pokusić się o konwersję testu do poziomu API i jeszcze bardziej przyspieszyć jego wykonywanie.

Odpowiednie wykorzystanie waitów

Zawartość aplikacji może być wyświetlana statycznie lub dynamicznie. Do dynamicznego wyświetlania treści wykorzystywana jest technologia AJAX, w której komunikacja z serwerem odbywa się w sposób asynchroniczny, bez konieczności przeładowania całego dokumentu. Dzięki temu elementy są ładowane na stronie w różnych odstępach czasu.

Niestety, zachowanie to wymusza stosowanie odpowiedniego podejścia do interakcji z dynamicznie ładowanymi elementami. Nie zaleca się stosowania statycznej metody Thread.Sleep(), która wstrzymuje wykonywanie kodu na określony czas. Powoduje to niepotrzebne wydłużenie czasu trwania testu, a w przypadku gdy żądany element zostanie załadowany szybciej niż zadeklarowana pauza – program będzie czekał bezczynnie, aż upłynie określona jednostka czasu przekazana do metody. W przypadku gdy element nie zostanie załadowany przed określonym czasem – test zakończy się niepowodzeniem (istnieje wiele czynników mających wpływ na załadowanie się elementu (obciążenie serwera, przepustowość sieci itd.), czasem może zająć kilka, czasem kilkanaście sekund). Zbyt długi czas oczekiwania może wpływać też na stabilność testów, które sporadycznie mogą kończyć się niepowodzeniem.

Wiele frameworków do automatyzacji (np. Selenium, Cypress, Playwright czy Spock) zawierają zbudowane rozwiązania… Warto też w tym miejscu wspomnieć o zewnętrznych bibliotekach jak np. Awaitility, które mogą rozszerzać funkcjonalności innych narzędzi. Do tego celu można zaimplementować metody, które będą czekały na dalsze wykonanie kodu, dopóki określony warunek nie zostanie spełniony (conditional waits). Dodatkową zaletą tego podejścia jest możliwość zdefiniowania tych metod w sposób globalny i używania ich bez duplikowania kodu w testach, co ułatwia również utrzymanie repozytorium.

Dedykowana baza danych do testów

Niewątpliwą zaletą jest odseparowanie danych od reszty środowisk wykorzystywanych w projekcie, dzięki czemu procesy nie są wzajemnie zaburzane – nagłe wdrożenie w danym środowisku wspólnie wykorzystywanym przez cały zespół lub zmiany wprowadzane manualnie nie będą miały wpływu na wykonywanie testów. Do stworzenia takiej bazy można wykorzystać bazę produkcyjną, dzięki czemu otrzymamy środowisko testowe bardzo zbliżone do tego, z którego korzystają końcowi użytkownicy aplikacji.

Wykorzystując osobną bazę danych, należy pamiętać o odpowiednim zarządzaniu nią – np. resetowaniu danych, gdy testy zostaną zakończone.

Wykorzystanie trybu headless

Tryb headless oznacza, że przeglądarka zostaje uruchomiona bez graficznego interfejsu. Jej działanie oraz funkcje pozostają niezmienne w stosunku do klasycznie uruchomionej przeglądarki, lecz zasoby nie są dodatkowo obciążone renderowaniem aplikacji internetowej oraz samym uruchomieniem interfejsu przeglądarki. Uruchamianie testów z wykorzystaniem tego trybu pozwoli na redukcję czasu wykonywania testów względem testów uruchamianych przy użyciu normalnej przeglądarki. Jest to przydatne w szczególności gdy uruchamiamy wiele testów jednocześnie – przeglądarki z aktywnym GUI wykorzystują o wiele więcej zasobów niż te uruchamiane w trybie headless. Dodatkowym atutem jest możliwość użycia ich do przeprowadzenia testów po stronie serwera lub kontenerów, a stąd prosta droga do włączenia ich w proces CI/CD.

Odpowiedni sprzęt do wykonywania testów

Wykonywanie testów, w szczególności E2E, w przeglądarkach wymaga dosyć dużej mocy obliczeniowej. Testy wykonują wiele operacji (uruchomienie instancji przeglądarki, renderowanie aplikacji, wykonanie kodu testów itd.). Przeprowadzanie ich na słabym wydajnościowo sprzęcie, zwłaszcza jeśli odbywa się to równolegle, może nie tylko negatywnie wpływać na czas ich wykonania, ale też prowadzić do niestabilności. Najprostszym rozwiązaniem jest aktualizacja komputera / serwera, na którym wykonujemy testy. Pozwoli to zwiększyć szybkość wykonywanych testów (oraz liczbę testów mogących być uruchamianych równolegle).

Gdy istnieją powody, dla których aktualizacja obecnego sprzętu nie jest możliwa, dobrym rozwiązaniem może okazać się skorzystanie z usług chmurowych.

Podsumowanie

Odpowiednie narzędzie do automatyzacji testów na pewno pomoże usprawnić cały proces, ale to tester automatyzujący, jako osoba odpowiedzialna za testy, powinien przestrzegać dobrych praktyk i promować ich użycie w projekcie. Wykorzystując powyższe wskazówki, można przyspieszyć wykonywanie testów automatycznych, dzięki czemu informacje na temat niezawodności testowanej aplikacji będą dostępne szybciej. Jest to istotne z punktu widzenia biznesowego – konkurencja nie śpi, klienci i użytkownicy końcowi oczekują, że wydawana przez nas aplikacja będzie działała płynnie i niezawodnie, a wszelkie poprawki zostaną wprowadzone najszybciej, jak to możliwe. Dzięki szybkiemu procesowi testowemu będziemy w stanie sprostać tym oczekiwaniom.

Stosowanie wymienionych przeze mnie metod jednocześnie nie jest obligatoryjne, ale wykorzystywanie każdej z nich pozwoli skrócić czas wykonywania testów o kilkadziesiąt minut lub nawet kilka/kilkanaście godzin, gdy testów jest uruchamianych naprawdę sporo.

Istotne jest okresowe sprawdzanie zestawu testów i dyskusje na temat możliwości optymalizacji pod każdym względem – każdy członek zespołu odpowiedzialnego za wytwarzanie oprogramowania powinien sugerować poprawki, dzięki czemu otrzymamy zdywersyfikowany feedback (z punktu widzenia technicznego i biznesowego), na podstawie którego można prowadzić pogłębione dyskusje i wyciągać odpowiednie wnioski, wykorzystując je do optymalizacji posiadanych testów.

Tester automatyzujący z kilkuletnim doświadczeniem w testowaniu aplikacji webowych, mobilnych oraz desktopowych. Prywatnie czyta książki, najchętniej z gatunku sci-fi. Miłośnik jazdy na rowerze. Szczęśliwy mąż i ojciec dwójki dzieci.

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, aby pobrać plik

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Dziękujemy za zapis na newsletter — został ostatni krok do aktywacji

Potwierdź poprawność adresu e-mail klikając link wiadomości, która została do Ciebie wysłana w tej chwili.

 

Jeśli w czasie do 5 minut w Twojej skrzynce odbiorczej nie będzie wiadomości to sprawdź również folder *spam*.

Twój adres e-mail znajduje się już na liście odbiorców newslettera

Wystąpił nieoczekiwany błąd

Spróbuj ponownie za chwilę.

    Get notified about new articles

    Be a part of something more than just newsletter

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address, telephone number and Skype ID/name for commercial purposes.

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address and telephone number for marketing purposes.

    Read more

    Just one click away!

    We've sent you an email containing a confirmation link. Please open your inbox and finalize your subscription there to receive your e-book copy.

    Note: If you don't see that email in your inbox shortly, check your spam folder.