Wzorce projektowe i dobre praktyki w automatyzacji testów

Wzorce projektowe i dobre praktyki w automatyzacji testów
Automatyzacja testów stanowi integralną część procesu wytwarzania oprogramowania. Skuteczne wdrożenie automatyzacji wymaga jednak przemyślanego podejścia i zastosowania sprawdzonych wzorców projektowych. Właściwie dobrane wzorce pozwalają tworzyć kod testów, który jest stabilny, łatwy w utrzymaniu i skalowalny.

Wzorzec projektowy (ang. design pattern) to sprawdzone w praktyce rozwiązanie często pojawiających się problemów projektowych. Pamiętajmy, że jest on opisem rozwiązania, a nie jego implementacją. 

Wzorzec opakowania strony 

Page Object Model (POM) to jeden z fundamentalnych wzorców w automatyzacji testów aplikacji webowych. Jego głównym założeniem jest reprezentowanie każdej strony aplikacji jako osobnej klasy. Klasa ta zawiera zarówno definicje elementów interfejsu użytkownika, jak i metody wykonujące na nich operacje. Dzięki takiemu podejściu logika testowa zostaje odseparowana od szczegółów implementacyjnych związanych z lokalizacją i obsługą elementów strony. W praktyce często zdarza się, że klasą reprezentowana jest nie cała strona, a jej funkcjonalny komponent, jak panel nawigacyjny czy formularz logowania.

Implementacja POM opiera się na koncepcji Page Factory, która umożliwia inicjalizację elementów strony poprzez adnotacje. W przypadku Selenium WebDriver elementy deklarowane są z wykorzystaniem adnotacji @FindBy wraz z określeniem sposobu ich lokalizacji. Następnie są one inicjalizowane w konstruktorze klasy poprzez wywołanie metody PageFactory.initElements(). Takie rozwiązanie znacząco upraszcza zarządzanie lokatorami elementów i sprawia, że kod staje się bardziej przejrzysty.

Rozszerzeniem podstawowego wzorca POM jest Module Object Pattern. W tym podejściu strona internetowa jest dzielona na mniejsze, funkcjonalne moduły, gdzie każdy moduł jest reprezentowany przez osobną klasę. Pozwala to na lepszą organizację kodu w przypadku złożonych aplikacji webowych i ułatwia ponowne wykorzystanie komponentów.

Wzorce zapewniające stabilność testów

Action Wrapper Pattern to wzorzec skupiający się na stabilności wykonywania testów. Zakłada on tworzenie własnych metod opakowujących standardowe akcje, które zawierają w sobie mechanizmy oczekiwania na określone zdarzenia. Eliminuje to konieczność ręcznego dodawania "waitów" w kodzie testowym i zmniejsza ryzyko występowania fałszywie negatywnych wyników spowodowanych problemami z synchronizacją.

Implementacja Action Wrapper Pattern może obejmować:

  • inteligentne mechanizmy oczekiwania na elementy
  • obsługę wyjątków specyficznych dla frameworka testowego
  • logowanie wykonywanych akcji
  • mechanizmy ponawiania nieudanych operacji

Hermetic Test Pattern koncentruje się na niezależności testów od stanu aplikacji i zewnętrznych zależności. Zgodnie z tym wzorcem, każdy test powinien sam zapewniać sobie niezbędne dane testowe i nie polegać na danych pozostawionych przez inne testy. Choć może to wydłużyć czas wykonania testów, znacząco zwiększa ich niezawodność i umożliwia równoległe uruchamianie.

Black Hole Proxy Pattern to kolejny wzorzec wspierający stabilność testów. Polega on na kontrolowanym odcinaniu dostępu do zewnętrznych serwisów i zastępowaniu ich uproszczonymi implementacjami. Pozwala to na eliminację nieprzewidywalnych zachowań związanych z integracją z zewnętrznymi systemami.

Wzorzec danych testowych

Default Values Pattern zakłada korzystanie z domyślnego zestawu danych testowych, który jest konfigurowany w jednym miejscu. Może być to realizowane poprzez pliki konfiguracyjne lub dedykowane generatory danych. Wzorzec ten rozwiązuje problem rozproszenia danych testowych w kodzie i ułatwia ich modyfikację.

Implementacja tego wzorca często wiąże się z utworzeniem:

  • centralnego repozytorium danych testowych
  • generatorów danych dla różnych przypadków testowych
  • mechanizmów walidacji danych testowych
  • systemu zarządzania stanem testowym

Wzorzec czytelności kodu

Fluent Interface to wzorzec poprawiający czytelność kodu testowego. Polega on na implementacji metod zwracających instancję klasy, co umożliwia łańcuchowanie wywołań. Zastosowanie tego wzorca sprawia, że kod testowy staje się bardziej deklaratywny i lepiej odzwierciedla intencje testera.

Przykładowo, tradycyjny kod:

loginPage.enterUsername("user");
loginPage.enterPassword("pass");
loginPage.clickLogin();

Może być zastąpiony bardziej płynną formą:

loginPage
    .enterUsername("user")
    .enterPassword("pass")
    .clickLogin();

Wzorzec organizacji testów

Behaviour Driven Development (BDD) to podejście, które postrzegane jest jako proces wytwarzania oprogramowania i  wykracza poza tradycyjne wzorce projektowe, wprowadzając nową warstwę abstrakcji w postaci scenariuszy pisanych językiem naturalnym. Może być jednak traktowany jako wzorzec w podejściu do samej automatyzacji testów. W tym kontekście BDD pomaga w:

  • organizacji testów zgodnie z wymaganiami biznesowymi
  • tworzeniu dokumentacji testowej zrozumiałej dla wszystkich interesariuszy
  • standaryzacji kroków testowych
  • ponownym wykorzystaniu komponentów testowych

Anty-wzorce w automatyzacji testów

Przy dyskusji o dobrych wzorcach warto pamiętać również o ich antonimie -  antywzorcach (anti-pattern). Są to przypadki powtarzających się złych rozwiązań problemów. Uczymy się ich, by można je było łatwo wykryć oraz by unikać ich w przyszłości.

Chain Linked Pattern to wykorzystanie danych z poprzedniego testu. Mimo pozornych korzyści w postaci szybszego wykonania testów, przy bliższym przyjrzeniu się wymusza wykonywanie testów w określonej kolejności co wprowadza szereg problemów :

  • utrudnione debugowanie w przypadku awarii testu
  • niemożność równoległego wykonywania testów
  • trudności w izolowaniu problemów
  • skomplikowane zarządzanie stanem testowym.

Spaghetti Code testów automatycznych to po prostu marnie napisany kod. W kontekście testów automatycznych objawia się np. poprzez:

  • nadmierną ilość instrukcji warunkowych
  • brak struktury i organizacji kodu
  • duplikację logiki testowej
  • trudności w utrzymaniu i modyfikacji testów.

Flaky tests to testy automatyczne, które nie dostarczają dokładnych i spójnych wyników w czasie. Mają tendencję do losowego niepowodzenia, mimo że funkcjonalność aplikacji pozostaje bez zmian. Takie zachowanie testów prowadzi do zmniejszonego zaufania zespołu do zestawu testów, co często skutkuje ignorowaniem pojawiających się błędów oraz do utrudnionego wykrywania i naprawiania rzeczywistych defektów w oprogramowaniu. 

Przyczynami występowania flaky tests są:

  • niewystarczająca ilość danych testowych, która nie pokrywa wszystkich scenariuszy
  • zbyt wąski zakres środowiska testowego, niepozwalający na pełną weryfikację
  • złożoność używanych technologii wprowadzająca dodatkowe zmienne
  • niewłaściwe praktyki implementacji testów, ignorujące zasady niezawodności

Automatyzacja wszystkiego to częsty anty-wzorzec w automatyzacji testów, polegający na próbie zautomatyzowania wszystkiego naraz. Takie podejście prowadzi do szeregu negatywnych konsekwencji, jak niestabilne skrypty testowe powstałe w pośpiechu, które nie zapewniają wiarygodnych wyników, czy niewystarczające pokrycie testami kluczowych funkcjonalności systemu. Odzwierciedleniem tego antywzorca są też częste awarie testów wymagające dodatkowego czasu i zasobów na naprawy i marnowanie zasobów zespołu na utrzymanie wadliwej automatyzacji

Przyczyny występowania tego anty-wzorca to najczęściej:

  • nierealistyczne terminy narzucone przez kierownictwo
  • presja na szybkie osiągnięcie wyników porównywalnych z testami manualnymi
  • brak strategicznego podejścia do automatyzacji
  • przecenienie możliwości zespołu automatyzującego w danym czasie

Dobre praktyki w implementacji wzorców

Implementując wzorce projektowe w automatyzacji testów, należy pamiętać o podstawowych zasadach:

  1. DRY (Don't Repeat Yourself) - eliminacja duplikacji kodu poprzez tworzenie wielokrotnie używanych komponentów
  2. KISS (Keep It Simple, Stupid) - utrzymywanie prostoty rozwiązań i unikanie nadmiernej komplikacji
  3. YAGNI (You Ain't Gonna Need It) - implementacja tylko niezbędnych funkcjonalności
  4. Separation of Concerns - oddzielenie logiki testowej od szczegółów implementacyjnych
  5. SOLID – czyli zbiór pięciu fundamentalnych reguł projektowania obiektowego, stworzonych przez Roberta C. Martina: 
    1. Single-responsibility Principle (Zasada Pojedynczej Odpowiedzialności) (klasa powinna mieć tylko jeden powód do zmiany, a każda klasa powinna być odpowiedzialna za pojedyncze zadanie)
    2. Open-closed Principle (Zasada Otwarte-Zamknięte) (klasy powinny być otwarte na rozszerzanie, ale zamknięte na modyfikacje, a zamiast modyfikować istniejący kod, należy go rozszerzać)
    3. Liskov Substitution Principle (Zasada Podstawienia Liskov) (klasy pochodne muszą być możliwe do zastąpienia przez ich klasy bazowe, a obiekty klas pochodnych powinny zachowywać się tak samo jak obiekty klas bazowych)
    4. Interface Segregation Principle (Zasada Segregacji Interfejsów) (klienci nie powinni być zmuszani do zależności od interfejsów, których nie używają, lepiej jest mieć wiele specyficznych interfejsów niż jeden ogólny)
    5. Dependency Inversion Principle (Zasada Odwrócenia Zależności) (moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych, oba typy modułów powinny zależeć od abstrakcji)

Dodatkowo warto zwrócić uwagę na:

  • właściwe nazewnictwo klas, metod i zmiennych
  • konsekwentne stosowanie przyjętych konwencji
  • dokumentowanie kodu
  • regularne przeglądy i refaktoryzację kodu testowego

Narzędzia wspierające implementację wzorców

Współczesne frameworki testowe oferują szereg narzędzi ułatwiających implementację opisanych wzorców:

  1. Selenium WebDriver:
    1. Page Factory do inicjalizacji elementów strony
    2. Wait mechanizmy do synchronizacji
    3. EventFiringWebDriver do logowania zdarzeń
  2. TestNG/JUnit:
    1. Adnotacje do konfiguracji testów
    2. Mechanizmy parametryzacji
    3. Obsługa zależności między testami
  3. Cucumber:
    1. Wsparcie dla BDD
    2. Integracja z Page Object Model
    3. Reużywalne kroki testowe

Zastosowanie odpowiednich wzorców projektowych w połączeniu z dobrymi praktykami programistycznymi pozwala tworzyć stabilne i skalowalne rozwiązania automatyzacyjne. Jest to szczególnie istotne w kontekście rosnącej złożoności aplikacji i potrzeby szybkiego dostarczania wysokiej jakości oprogramowania. 

Cheat sheet od testerzy.pl

Subskrybujesz platformę testerzy+? Całą wiedzę z tego artykułu zebraliśmy w skondensowaną formę jednostronicowego cheat sheet:

wzorce-projektowe-miniatura-1.jpgZaloguj się na swoje konto i pobierz materiał tutaj

Źródła:
https://www.slideshare.net/sjsi/wzorce-projektowe-w-automatyzacji-testw-aplikacji-webowych#1
https://justjoin.it/blog/wzorzec-projektowy-musisz-znac-zalety-page-object-model
https://www.browserstack.com/guide/design-patterns-in-automation-framework
https://medium.com/@dees3g/test-automation-design-patters-boosting-efficiency-and-code-quality-f2e036cd953e
https://www.testdevlab.com/blog/5-test-automation-anti-patterns-and-how-to-avoid-them

To powinno Cię zainteresować