Jakie powinny być testy automatyczne w Continuous Integration i jak to zapewnić?

AA-remake

Celem testów automatycznych jest jak najszybsze dostarczenie informacji na temat jakości aplikacji. Żeby tego dokonać, muszą spełniać wiele warunków. Od najbardziej oczywistych, czyli sprawdzać to, co testy sprawdzać powinny, aż do szybkości działania. Jeżeli testy automatyczne są używane w kontekście CI, największy nacisk kładzie się na szybkość wykonania testów, łatwość utrzymania i dopisywania kolejnych, oraz stabilność (zaufanie do wyników).

Te trzy wysoko poziomowe zadania przekładają się na konkretne wymagania co do samego kodu testów automatycznych. Na ten temat jest wiele opracowań, przedstawiających wyliczenia dziesiątek takich warunków. Moje długoletnie doświadczenie w automatyzacji testów w środowisku CI pozwoliło mi stworzyć własną listę najważniejszych cech. Pozycje z tej listy dla niektórych będą zupełnie oczywiste, ale co nie raz mnie wprawiło w zdumienie, i skłoniło do stworzenia tej prezentacji, niektórzy inżynierowie testów, nawet ze sporym doświadczeniem nigdy o nich nie słyszeli. Poniższa lista będzie najbardziej przydatna dla tych, przed którymi stoi zdanie stworzenia od początku zestawu testów automatycznych.

Wszystkie najważniejsze cechy testów przedstawię na przykładzie zautomatyzowanej regresji dla webowej aplikacji dla firm spedycyjnych i transportowych. Obecnie mamy 200 testów, których czas trwania to 12 godzin. Jesteśmy w stanie uruchomić je wszystkie w nieco ponad 30 minut, co z analizą wyników powoduje, że o ile nie będzie po drodze błędów, jesteśmy w stanie wydawać na produkcję nawet dwie wersje dziennie.

Szybkość testów:

Sposobów na szybkie testy można sypać jak z rękawa i można by obdzielić nimi nie jedną prezentację, min.: Kategorycznie nie używać waitów (sleepów), implicitly wait, każde oczekiwanie realizować przez explicit wait, ograniczyć testy UI, nie automatyzować długich ścieżek e2e, itp. Itd. Jednak największą i często niedocenianą cechą testów jest

możliwość zrównoleglenia uruchomienia testów.

Matematyka jest prosta, jeżeli mamy 10 testów, z których każdy zajmuje średnio 3 minuty, puszczanie ich sekwencyjne potrwa 30 minut. Puszczenie równoległe: czas najdłuższego testu.

W zasadzie są dwa warunki konieczne do uzyskania możliwości zrównoleglenia testów: technologiczne możliwości i logiczna niezależność testów od siebie.

– technologiczne możliwości:

Obsługa wielu wątków przez język którego będziemy używać do automatyzacji, oraz posiadanie zasobów, na których możemy te testy uruchamiać (albo laboratorium z maszynami, albo dostęp do odpowiedniej usługi online),

– bezwzględna niezależność testów od siebie.

Każdy test powinien sam zapewnić sobie wszystkie wymagania do uruchomienia. Niemal w każdym projekcie, w którym brałem udział ktoś, na którymś etapie proponował, żeby jeden test tworzył obiekt w aplikacji, a drugi go kasował. Oprócz wprowadzenia niepotrzebnych problemów z przekazywaniem danych pomiędzy testami, takie podejście uniemożliwia proste zrównoleglenie, oraz powoduje, że w wypadku błędu przy tworzeniu obiektu w aplikacji, pozostałe testy po prostu się nie wykonają.

W rozwiązaniu, które przedstawię, technologiczne możliwości są spełnione poprzez użycie javy, junit i surefire’a, a logiczna niezależność testów, przez użycie adnotacji @Before, @After, oraz przygotowanie danych przez API.

Przykłady:

Fragmenty sekcji @Before i @After, konfiguracja surefire plugina w POM’ie, wywołania API.

Wynikająca z powyższych założeń cechą jest często również niedoceniana możliwość uruchomienia natychmiast, niezależnie od innych i niezależnie od środowiska, każdego testu z naszego zestawu, co często ogranicza retesty fixa do kilku minut.

Łatwość utrzymania i dodawania nowych testów.

Przekonywanie kogokolwiek do Page Object Pattern, to truizm. Odpowiednio zaprojektowane rozwiązanie z abstrakcyjną klasą strony, ma w zasadzie same plusy i pozwala na szybką zmianę testów w reakcji na zmiany w aplikacji, oraz dopisywanie kolejnych testów, oraz metod w ramach stron. Nie do przecenienia jest również szybkość wprowadzenia kolejnych inżynierów testów w projekt.

Są jednak elementy, których stosowanie w ramach Page Object Pattern decyduje czy nasz framework okaże się wystarczająco dobry w obliczu największy wyzwań, którymi dla testów UI są – zmiany layoutu aplikacji, oraz problemy ze znienawidzonym Stale Object Reference. Moim zdaniem są to:

Helpery

Nie używamy „gołych” metod, np. WebDrivera typu click(), getText(), getAttribute(), sendText() itp. Wszystkie interakcje z kontrolkami przeglądarki powinny być realizowane naszymi własnymi helperami, które oprócz wykonania danej akcji, będą miały dodaną, zgodnie z potrzebą, obsługę oczekiwania na dany element, sprawdzenie poprawności jego wyświetlania, wcześniejsze czyszczenie pola, czy obsługę niesławnego Stale Element Exception.

Przykłady: helpery z mojego projektu

Wspólne lokatory

Kolejny truizm to trzymanie lokatorów do elementów na każdej stronie w jednym miejscu. Jednak warto pójść o krok dalej. W zależności od użytego frameworka, możliwe, że nasze lokatory mają części wspólne. W takim wypadku należy te części wspólne wynieść do innego miejsca, np. do abstrakcyjnej klasy strony, bądź zastosowanie generycznych obiektów w aplikacji.

Abstrakcyjne metody

Jeżeli jest to możliwe w testach powinny zostać wydzielone metody, które mogą być zastosowane do

standardowych obiektów w aplikacji, niezależnie na jakiej stronie się znajdują. Powoduje to, że dodanie kolejnej strony do naszego projektu w POP trwa jeszcze szybciej, a inżynier ją kodujący nie musi rozwiązywać ponownie już rozwiązanych zadań.

Przykłady: podobne elementy w aplikacji, oraz odpowiadające im wydzielone lokatory z projektu. Generyczna metoda obsługi weryfikacji tabeli w aplikacji.

Stabilność i elastyczność:

I znów sposobów na powyższe jest mnóstwo. Dla mnie najważniejsze, to użycie sprawdzonej, stabilnej technologii (np. Java, WebDriver, ale również UFT i inne ‘kombajny’), użycie helperów, opisane już wyżej, oraz możliwość uruchamiana testów na dowolnym środowisku.

Dzięki odpowiedniemu skonstruowaniu abstrakcyjnej klasy testu jesteśmy w stanie parametrem wejściowym do naszego testu sterować gdzie testy mają być uruchomione. Podczas developowania testów na własnym komputerze, uruchamiamy lokalny serwer Selenium, ale gdy puszczamy testy z Jenkinsa, w ramach CI, przesyłając odpowiedni parametr, uruchamiamy cały zestaw na serwerze jednego z usługodawców przeglądarek w chmurze.

Stosowanie elastycznego i otwartego języka programowania umożliwia szybkie dodawanie do projektu nowych komponentów, sprawdzających kolejne obszary aplikacji. Przykładem może byś sprawdzanie emaili wysyłanych przez aplikację, czy generowanych plików pdf, czy xls.

Przykład:

Przykład z abstrakcyjnej klasy testów. Przykłady klas do sprawdzania emaili i zawartości plików pdf.

Podsumowanie:

Przegląd środowiska CI: Jenkinsa. Workflow, joby umożliwiające uruchomienie testów na różnych środowiskach, w różnych konfiguracjach i różnych podzbiorach.

Tagged under:

Program konferencji

TwitterFacebookLinkedInGoogle+