Sytuacja kobiet w IT w 2024 roku
28.08.20216 min
Bartłomiej Caputa

Bartłomiej CaputaTest EngineerEQTek

8 błędów przy tworzeniu testów automatycznych i jak je naprawić

Sprawdź, jakich 8 błędów warto unikać podczas tworzenia testów automatycznych oraz dowiedz się, w jaki sposób zastąpić je poprawnymi praktykami

8 błędów przy tworzeniu testów automatycznych i jak je naprawić

Testowanie stało się nieodłącznym elementem cyklu wytwarzania oprogramowania. Jedną z części tego procesu jest automatyzacja testów. Niewątpliwie pozwala zaoszczędzić dużo czasu i pracy, często żmudnej i powtarzalnej. Jednak trzeba pamiętać, że automatyzacja testów nie polega jedynie na tworzeniu testów. Aby właściwie spełniały swoją rolę, równie ważne jest ich utrzymywanie. W przeciwnym razie mogą stać się udręką i koszmarem każdego testera.

Jak tego uniknąć? Wystrzeganie się kilku poniższych błędów z pewnością pomoże utrzymać nasz framework testowy w odpowiedniej kondycji. Przykłady zostały napisane w Javie.

Thread.sleep();



Kto nigdy nie użył metody Sleep w swoim teście niech pierwszy rzuci klawiaturą. Jest to najprostszy sposób opóźnienia wykonania kolejnej instrukcji, aczkolwiek mocno wydłuża czas wykonania testu. Może powodować również losowe niepowodzenia testu, na przykład w sytuacji gdy strona ładuje się dłużej niż zazwyczaj.

Czego użyć w zamian?


- AjaxElementLocatorFactory

Jeżeli nasza klasa implementuje PageFactory (czyli inicjalizujemy wszystkie WebElementy w momencie tworzenia obiektu danej klasy), możemy użyć klasy AjaxElementLocatorFactory.

WebDriver driver = new ChromeDriver();
PageFactory.initElements(new AjaxElementLocatorFactory(driver, 15), this);


Gdy chcemy wykonać operację na elemencie z tej klasy, obiekt driver będzie czekał do 15 sekund aż element zostanie wyświetlony, po czym zwróci wyjątek NoSuchElementException.


- FluentWait, ExplicitWait

Klasy te są do siebie bardzo zbliżone. Tworząc obiekty tych klas możemy zdefiniować m.in. maksymalny czas oczekiwania, częstotliwość sprawdzania warunku czy w przypadku FluentWait własny warunek na spełnienie którego chcemy czekać.

Wait wait = new FluentWait(driver)
        .withTimeout(Duration.ofSeconds(15))
        .pollingEvery(Duration.ofSeconds(1))
        .ignoring(Exception.class);

wait.until((Function<WebDriver, Boolean>) d -> d.findElements(By.id("foo"))
        .size() == 2);


- ImplicitWait

Jest definiowany globalnie, czyli zostanie zastosowany do każdej operacji typu findElement. Określamy w nim jedynie maksymalny czas, który będziemy czekać na pojawienie się WebElementu. Zdarza się, że znacznie wydłuża czas wykonywania testów. 

Nie powinniśmy używać jednocześnie różnych klas typu Wait. Może to spowodować nieoczekiwane zwiększenie czasu wykonywania testów do wartości których nie będziemy w stanie przewidzieć.

Grupowanie asercji

Assert.assertTrue(Button_Submit.isDisplayed() && Input_Address.isDisplayed() &&
        Input_FirstName.isDisplayed() && Input_LastName.isDisplayed() &&
        Checkbox_Confirmation.isDisplayed() && Container_Menu1.isDisplayed());


Niewątpliwie powyższe rozwiązanie jest czytelne i zajmuje niewiele miejsca jak na ilość informacji którą sprawdza. Ale czy dobrze spełnia swoją funkcję? Załóżmy, że przez nieprawidłowy selektor, test zakończył się niepowodzeniem w miejscu tej asercji. Aby sprawdzić, który element nie został znaleziony, musimy manualnie sprawdzić wszystkie selektory.

Czego użyć w zamian?

Lepszym rozwiązaniem będzie taki zapis:

Assert.assertTrue(Button_Submit.isDisplayed());
Assert.assertTrue(Input_Address.isDisplayed());
Assert.assertTrue(Input_FirstName.isDisplayed());
Assert.assertTrue(Input_LastName.isDisplayed());
Assert.assertTrue(Checkbox_Confirmation.isDisplayed());
Assert.assertTrue(Container_Menu1.isDisplayed());


Gdy zdarzy się błąd, będziemy znać dokładną linijkę i element, którego dotyczy.

try/catch everything!

Jak upewnić się czy element nie występuje na stronie?

Assert.assertFalse(Button_Submit.isDisplayed());


Powyższe rozwiązanie zadziała gdy element będzie ukryty i obecny w kodzie strony. W niektórych przypadkach elementy nie wyświetlane, nie są również obecne w kodzie. W takiej sytuacji test zakończy się niepowodzeniem z wyjątkiem NotFoundElementException.

Spotkałem się również z takim rozwiązaniem:

try{
    Assert.assertTrue(Container_Menu1.isDisplayed());
}catch(Exception e){
    Assert.assertTrue(true);
}


Warto zauważyć, że w przypadku wystąpienia jakiegokolwiek wyjątku, test nie zakończy się niepowodzeniem.

Czego użyć w zamian?

Alternatywą może być użycie listy WebElementów, aby następnie w asercji sprawdzić rozmiar listy. Jeżeli będzie wynosił 0, oznacza to, że element nie jest wyświetlony na stronie. Wadą tego rozwiązania jest wydłużony czas wykonania testu, o timeout zdefiniowany w obiekcie Wait. Jeżeli zwiększony czas wykonywania testu jest dla nas nie do zaakceptowania, możemy ostrożnie zmniejszyć timeout dla tej operacji, po czym przywrócić go do normalnej wartości.

Xpaaaaath, czyli długie i niestabilne selektory

Na początku testerskiej kariery, zapewne niejeden z nas wspomagał się opcją kopiowania xpatha z narzędzi deweloperskich przeglądarki. Najczęściej wygląda on jak poniżej:

@FindBy(xpath="/html/body/div/div/section[3]/div/div/div[1]/div[4]/div")
WebElement Button_Cancel;


Po samej jego długości możemy stwierdzić, że nie będzie on wzorem stabilności. W sytuacji gdy na stronie zostanie dodany nowy element lub obecny zostanie zmieniony, ten selektor zapewne przestanie działać.

Czego użyć w zamian?

Jeżeli element do którego definiujemy selektor posiada ID, najpewniej będzie użyć właśnie jego. W przeciwnym przypadku, najlepiej użyć CSS selektora lub xpath, właśnie w takiej kolejności. Selektory CSS zazwyczaj są prostsze i mniej złożone. W niektórych przypadkach jednak nie da się ich zastosować, na przykład gdy chcemy znaleźć element po jego tekście lub przejść do rodzica elementu. Najlepiej jest tworzyć selektory możliwie najkrótsze, które jednoznacznie wskazują na element który jest naszym celem.


Nawigowanie przez UI

Wyobraźmy sobie, że naszym zadaniem jest napisanie kilku testów do strony z ogłoszeniami lokalnymi. Aby było nam łatwiej, developerzy udostępnili API, w którym możemy utworzyć dowolne ogłoszenie przed testem. Jednym ze sposobów wyświetlenia strony z ogłoszeniem przez użytkownika, jest wybranie odpowiednich opcji z menu i wpisanie słowa kluczowego lub numeru ogłoszenia do wyszukiwarki.

Często wszystkie takie testy mają wspólny mianownik, jest nim logowanie (którego zazwyczaj nie da się pominąć) oraz przejście do strony, którą chcemy zweryfikować. Ten drugi etap powoduje znaczne wydłużenie czasu trwania i obniżenie niezawodności testów. Naszym celem nie jest również testowanie menu czy wyszukiwarki, a testy interfejsu powinny być pokryte w ramach innych testów (e2e). Gdy tylko istnieje możliwość, powinniśmy unikać nawigowania przez interfejs graficzny i posłużyć się na przykład metodą get:

driver.get(baseUrl+"/advert/"+adNumber);


Testowanie interfejsu powinno być pokryte w ramach testów e2e.

Statyczne dane testowe

Naszym celem będzie napisanie kolejnego testu, do system opisanego w poprzednim punkcie. Test ma sprawdzić funkcję, która wymaga wcześniejszej konfiguracji konta użytkownika z panelu administratora. Konfiguracja ta zapisywana jest w bazie danych. Tworzymy więc ręcznie użytkownika, którym będziemy logować się za każdym razem i sprawdzać funkcję będącą celem testu.

Jest to dosyć powszechna praktyka, w której czas potrzebny na utrzymanie testu jest dużo większy niż czas zaoszczędzony na implementacji. Baza danych może zostać wyczyszczona lub inny tester korzystając z konta administratora zmieni konfigurację użytkownika użytego w teście. Test nie działa, a my poświęcamy czas aby wdrożyć się ponownie bo nie pamiętamy co dokładnie było jego celem. Gdy zaistnieje potrzeba nie ma szybkiej możliwości uruchomienia takich testów na innej bazie danych.

Rozwiązanie w tym przypadku należy dobrać do specyfiki produktu i środowiska w którym pracujemy. Najlepiej gdy dane testowe są tworzone bezpośrednio przed testem, przez API lub dodawane do bazy. Mamy wtedy pewność, że uruchomimy je na każdej bazie i środowisku, a praca innych testerów nie zakłóci przebiegu testów. W przypadku gdy usuwamy dane testowe po zakończonym teście, powinniśmy upewnić się, że jest to wykonywane niezależnie od wyniku testu.

Nieprawidłowe nazwy testów

Dobrą praktyką jest wyrobić sobie nawyk odpowiedniego opisywania testów. Test o nazwie FormFieldsValidation nie powinien sprawdzać również wysyłania pliku czy informacji o wysłaniu tego pliku w historii.

Testy powinny być możliwie krótkie i zdefiniowane przez odpowiednią nazwę. Zdawkowe i niekompletne nazwy powodują problemy podczas tworzenia Test Planu, a niektóre części systemu mogą wydawać się niepokryte.

Testy bez asercji

Zdarza się, że testy, najczęściej te które sprawdzają przyznawanie dostępu do poszczególnych fragmentów systemu, nie mają asercji. Zakładamy, że jeżeli test przebiegł do końca bez błędów to strona została wyświetlona. Nic bardziej mylnego.

Testy bez asercji nie są testami, a jedynie akcjami wykonywanymi w aplikacji. Jeżeli test nie posiada asercji, upewniamy się jedynie, że nie nastąpił wyjątek od strony aplikacji. Zawsze powinniśmy upewnić się, że cel naszego testu jest taki jak oczekujemy, właśnie używając asercji.

Podsumowanie

Często pisząc kod w pośpiechu, uciekając przed zbliżającymi się deadlinem, istnieje zwiększone ryzyko popełnienia błędów. Spróbujmy nie ulegać presji czasu i starać się pisać jak najlepszy kod, który odwdzięczy się w utrzymaniu.

<p>Loading...</p>