Jakub Siewierski
Alior Bank
Jakub SiewierskiSenior Test Tools Developer @ Alior Bank

Jak stworzyć testy, które może pisać każdy

Sprawdź, jak automatyzować swoje testy przy pomocy frameworka Robot i zrezygnuj z XPath.
11.07.20227 min
Jak stworzyć testy, które może pisać każdy

Myślisz o wdrożeniu lub refaktoryzacji testów automatycznych? Daj się przekonać, że wiedza biznesowa mądrze suplementowana umiejętnościami technicznymi pomaga osiągnąć ten cel nie tylko efektywnie, ale i szalenie satysfakcjonująco!

Automatyzacja testów

To najprawdopodobniej słowa, które w głowie każdego ‘poszukiwacza błędów’ odmieniane są przez wszystkie możliwe przypadki. Wspominając o testerach, będę świadomie używał pierwszej osoby, ponieważ sam zaczynałem karierę w IT od tego stanowiska  i pomimo relatywnie dużej zmiany obowiązków, dalej się z nim silnie utożsamiam.

Już od etapu decyzji i pierwszych szkoleń jesteśmy zalewani informacjami o tym, że na horyzoncie jawi się kusząca możliwość tworzenia ‘automatów’, a pracujący znajomi, osoby na forach, blogi czy oferty pracy, rysują ścieżkę kariery na zasadzie skrętu w lewo lub w prawo — co  w wielkim uproszczeniu oznacza,  że jeżeli zdecydujemy się opuścić drogę technicznego specjalisty, to widzimy się bardziej w obszarze koordynacji i zarządzania testami, lub popędzimy wartko w stronę analizy.

Nie ma progresu bez znajomości procesu!

Sposób, w jaki tester rozwija karierę, staje się bardziej zrozumiały, gdy przyjrzymy się specyfice tej pracy. Spróbujmy na początek rozłożyć na czynniki podstawowy zakres obowiązków: zwykle zaczynamy od odtworzenia napisanych wcześniej scenariuszy, aby potem otrzymać do testów nową funkcjonalność, wykonujemy jedną, drugą, trzecią regresję, zgłaszamy błędy, upewniamy się co do zapisów w dokumentacji, uczestniczymy w cyklicznych spotkaniach na temat produktu, raportujemy wykonane testy… 

Wszystko to powoduje, że zyskujemy potężną broń – rozległą wiedzę na temat aplikacji, znamy jej front (o ile go posiada), API, okalające procesy i zależne serwisy, a zapytani – jesteśmy w stanie podać jej stan na tu i teraz uwzględniając podział na środowiska.

W pewnym momencie możemy zadać sobie pytanie ‘co dalej?’, po czym zaczynamy sobie przypominać o sławnej automatyzacji i coraz śmielej czujemy, że zredukowanie kliknięć i czynności, które codziennie wykonujemy w sposób manualny, poprawiłoby kondycję zarówno nam, jak i aplikacji, którą testujemy.

Wspólnymi siłami uporać się z testami!

Jednym z szeroko opisywanych dogmatów testowania jest stwierdzenie, że testy produktu nigdy się nie kończą. Może to naturalnie spowodować sytuację, że osoba testująca jest permanentnie zaangażowana w pracę.

Testy automatyczne wymagają dużego angażu, szczególnie na początku procesu wdrażania, lecz w obliczu zmieniających się wersji bibliotek, przeglądarek czy, co najważniejsze, samej testowanej aplikacji, nie mogą być pozostawione ‘samymi sobie’ i wymagają stałego utrzymania.

Często z tego tytułu powstają zespoły zorientowane typowo na automatyzację, które z wielu narzędzi wybierają to najodpowiedniejsze, nieraz podyktowane popularnością. Co za tym idzie, bogatym wsparciem i dokumentacją, co jest moim zdaniem decyzją jak najbardziej uzasadnioną.

Ostatnie zdanie można zakończyć kropką, jednak wstęp tego artykułu opisujący pracę testera manualnego nie był bezcelowy. Pokażę, w jaki sposób udało się nam nie tyle wykorzystać, ile zaangażować kompetencje testerów manualnych w procesie tworzenia testów automatycznych.

Przyszła ochota na wdrożenie ROBOT-a!

Założenie na pierwszy rzut oka było proste – skoro chcemy zacieśnić więzy współpracy z testerami manualnymi, to wypracowany proces musi być łatwy w obsłudze i czytelny, aby nawet osoby, które nie władają żadnym językiem programowania i nie parają się aspektami technicznymi środowiska, były w stanie przekuć swoją wiedzę biznesową na użyteczne skrypty.

Drugą, nie mniej ważną zaletą, była relatywnie duża ‘modalność’, która pozwoliłaby dostosowywać narzędzie pod wymagania projektów i przeniosłaby ciężar utrzymania i rozwijania frameworka na zespół techniczny.

Wybór padł na Robot Framework – czyli narzędzie oparte o słowa-klucze (keyword driven framework). Posiada relatywnie przejrzysty interfejs, dużą bazę aktywnych użytkowników a dostępne biblioteki wydawały się wystarczające do planowanych zastosowań.

Potężnym plusem było także oparcie frameworka na Pythonie z możliwością implementacji funkcji i bibliotek tego języka, co drastycznie zmniejszyło obawy dotyczące ewentualnych ograniczeń. 

Jak to zrobić, żeby wszystkim dogodzić ?

Od początku naszego projektowania staraliśmy się wejść w rolę użytkownika – czyli osoby, która będzie pisać skrypty testów automatycznych. Mając cały czas na uwadze główne wyznaczniki, którymi mamy się kierować, czyli czytelność i łatwość obsługi, zdecydowaliśmy się, że będziemy opierać się za zachowaniach podobnych do ludzkich, czyli każdy element w aplikacji rozpatrzymy w trzech krokach:


WYSZUKAJ -> WYKONAJ DZIAŁANIE -> POCZEKAJ


Wyszukaj: 

Tutaj odbyła się lwia część pracy. W Robot Framework możemy łatwo zaimportować biblioteki Selenium, gdzie metody są mapowane na słowa-klucze. Jednak zostajemy tym samym z koniecznością określania lokalizatorów. Niektóre z nich to właśnie XPath, czy też selektory CSS.

Naszym zdaniem nie spełniało to postawionego sobie celu związanego z czytelnością i utrzymaniem, dlatego zdecydowaliśmy się na krok, w którym definiujemy lokalizację elementu za pomocą etykiety widzianej przez użytkownika na ekranie.

Załóżmy taką formatkę przelewu krajowego:

Spróbujmy najpierw najmniejszym kosztem zmapować te elementy konieczne do przejścia dalej przez proces. Dla ułatwienia tam, gdzie możemy, użyjemy wyszukiwania po identyfikatorze. Niech nasze repozytorium elementów nazywa się TransferFields.

from selenium.webdriver.remote.webdriver import WebDriver as WD
from selenium.webdriver.remote.webelement import WebElement as WE
from dataclasses import dataclass, InitVar, field
 
@dataclass
class TransferFields():
    driver: InitVar[WD]
    source_account: str = field(init=False)
    beneficiary_name: str = field(init=False)
    beneficiary_account: str = field(init=False)
    amount: str = field(init=False)
    transfer_title: str = field(init=False)
    email_confirmation: str = field(init=False)
    proceed: str = field(init=False)
    
    def __post_init__(self, driver: WD):
        by_id = lambda id: driver.find_element_by_id(f"{id}")
        self.source_account = by_id('account.name')
        self.beneficiary_account = by_id('account_number')
        self.beneficiary_name = by_id('destination.name')
        self.amount = by_id('amount.value')
        self.transfer_title = by_id('title')
        self.email_confirmation = by_id('confirmation-to-email')
        
        self.proceed = driver.find_element_by_xpath("//button[@type[.='submit']]")


Szczęśliwie identyfikatory poszczególnych pól są dla nas czytelne. Mapując każdy element na atrybut klasy, naturalnie ułatwiamy sobie ich organizację i utrzymanie w przypadku zmian, jednak musimy sobie odpowiedzieć na pytania poniżej:

  • Czy byłbym w stanie efektywnie pisać testy w ‘czystym’ Selenium bez znajomości języka programowania?
  • Jak wpłynęłoby na czytelność kodu i utrzymanie pojawienie się identyfikatorów typu: ‘XH3893J’, lub odnalezienie elementu wymagałoby użycia skomplikowanych xPathów, lub CSS?


Odpowiedzi rysują się raczej negatywnie, dlatego potrzebowaliśmy rozwiązania uniwersalnego, zdatnego do bardzo prostej obsługi wielu aplikacji. W przypadku wspomnianych etykiet postanowiliśmy sprostać kilku technicznym założeniom:

  • Najczęstsze akcje wykonywane przez użytkowników w kontekście nawigacji po aplikacji to:
    • kliknięcie w element,
    • zaznaczenie checkboxa,
    • zaznaczenie radio,
    • wprowadzenie danych w pole tekstowe,
    • wybór wartości z listy rozwijalnej.
  • Identyfikatorami elementów powinien być tekst widziany na ekranie.


Dzięki temu udało nam się wypracować kilka uniwersalnych metod. Kod kryjący się za tymi definicjami wymagałby szerszego omówienia, dlatego na potrzeby przedstawienia idei podam przykładowe algorytmy:

    BUTTON  ${etykieta}                # Szukaj przycisku oznaczonego tekstem etykiety, kliknij go i poczekaj na przeładowanie aplikacji.
    SELECT  ${etykieta}  ${wartość}    # Szukaj elementu typu select, dropdown etc. w sąsiedztwie elementu etykiety, po czym wybierz dostępną dla niego wartość.
 
    INPUT   ${etykieta}  ${tekst}      # Szukaj elementu typu input, textarea etc. w sąsiedztwie elementu etykieta, po czym wprowadź do niego tekst
 
    CHECKBOX  ${etykieta}  ${stan}     # Szukaj elementu typu checkbox w sąsiedztwie elementu etykieta, sprawdź jego stan po czym kliknij, jeżeli wymaga zmiany stanu.
    RADIO  ${etykieta}                 # Szukaj elmentu typu radio w sąsiedztwie elementu etykieta po czym w niego kliknij
 
    VERIFY TEXT  ${text}               # Szukaj podanego tesktu w źródle strony widocznych elementów.

Wykonaj działanie

Oczyśćmy teraz głowę i spróbujmy nasz znajomy ekran formatki zautomatyzować przy pomocy Robot Framework. Do pełni szczęścia będziemy potrzebować tylko ogólnej i podstawowej wiedzy z budowy pliku robotowego.

Jako że importy wykonywane są za użytkownika po przesłaniu przez niego skryptu do wykonania, musi on znać tylko takie sekcje jak:

  • Variables, gdzie przechowywane są zmienne dla całego zestawu testów,
  • Keywords, gdzie możemy przechowywać zestawy instrukcji w jednej definicji, obsługuje parametry.
  • Test Cases, czyli nasze przypadki testowe.


Do dzieła:

*** Variables ***
 
*** Keywords ***
 
*** Test Cases ***
Formatka przelewu krajowego
  SELECT    Z rachunku    BullDog
  INPUT    Nazwa odbiorcy    Jan Testowy
  INPUT    Numer rachunku    PL00 0000 0000 0000 0000 0000 0000
  INPUT    Kwota przelewu    160,99
  INPUT    Tytuł przelewu    Przelew ROBOT
  BUTTON    Dodatkowe opcje
  CHECKBOX    Wyślij potwierdzenie na adres e-mail  ON
  BUTTON    Dalej


Czy nie wygląda to atrakcyjnie? Dodatkowo, jeżeli zamierzamy wykorzystać zestaw instrukcji do przyszłych przypadków, możemy go ‘spakować’ do sekcji keywords i określić argumenty:

*** Variables ***
 
*** Keywords ***
PRZELEW KRAJOWY
  [Documentation]  Keyword przechodzi przez obligatoryjne pola formatki przelewu krajowego do ekranu autoryzacji.
  [Arguments]    ${z_konta}=Bulldog    ${email}=ON
  SELECT    Z rachunku    ${z_konta}
  INPUT    Nazwa odbiorcy    Jan Testowy
  INPUT    Numer rachunku    PL00 0000 0000 0000 0000 0000 0000
  INPUT    Kwota przelewu    160,99
  INPUT    Tytuł przelewu    Przelew ROBOT
  BUTTON    Dodatkowe opcje
  CHECKBOX    Wyślij potwierdzenie na adres e-mail  ${email}
  BUTTON    Dalej
*** Test Cases ***
Krajowy z Bulldog z potwierdzeniem e-mail
    PRZELEW KRAJOWY
Krajowy z Konto Internetowe bez potwierdzenia e-mail
    PRZELEW KRAJOWY    z_konta=Konto Internetowe    email=OFF


Warto wspomnieć, że metody tego interfejsu możemy łatwo dziedziczyć na potrzeby innych projektów. Inputy w aplikacji B mają inne ścieżki niż w A? – Nie ma problemu! Selecty wymagają kliknięcia w specyficzny element nadrzędny? – Zrobione!

Ważne, żeby zdjąć ten ciężar z głowy użytkownika. Z jego perspektywy automatyzacja obu projektów będzie wyglądać tak samo.

Na zakończenie leci polecenie!

Oczywiście powyżej przedstawiony przykład to jest kropla w morzu możliwości tego frameworka. Zachęcam do odwiedzenia oficjalnej witryny dodatku: Robot Framework. Znajdziecie tam proces instalacji, a także szczegółowe opisy bibliotek i tutoriale.

Zakończę robotową frazą: Code is worth a thousand words.

<p>Loading...</p>