Testy automatyczne. Garść doświadczeń okiem praktykantów

Kim jesteśmy?                                     

Jak łatwo domyślić się po tytule wpisu, byliśmy praktykantami w firmie Syneo a do naszych zadań należało między innymi napisanie testów automatycznych do aplikacji internetowej. Na co dzień studiujemy na Uniwersytecie Technologiczno-Przyrodniczym w Bydgoszczy, na kierunku informatyka stosowana.

 

Czego używaliśmy do tworzenia testów?

  • Język: C#,
  • IDE: Visual Studio 2015,
  • Framework testujący: NUnit 3.4.1,
  • Narzędzie do obsługi przeglądarki: Selenium Web Driver,
  • Przeglądarka: Mozilla Firefox (wcześniej Google Chrome),
  • System kontroli wersji: Git + Visual Studio Team Services

 

O czym będziemy pisać?

Chcielibyśmy przedstawić kilka naszych spostrzeżeń dotyczących testów automatycznych z punktu widzenia początkującego programisty a także opisać wady i zalety narzędzi, z użyciem których je pisaliśmy. Spróbujemy także stwierdzić czy testy automatyczne wnoszą dużą wartość do projektu, dla którego są pisane. Zapraszamy więc do lektury.

 

Kilka wskazówek dla początkujących

Testy automatyczne były dla nas całkowitą nowością, uczyliśmy się na bieżąco w trakcie pisania. Oczywiście podczas tego procesu napotkaliśmy kilka problemów, których rozwiązanie często nie było już aż tak oczywiste. Dlatego chcielibyśmy przedstawić kilka wskazówek dla osób, które nigdy wcześniej nie zetknęły się z tym tematem.

 

  1. Zapoznaj się z aplikacją

 

Być może jest to trywialne stwierdzenie i dla wielu wyda się oczywiste, jednak przed rozpoczęciem pisania testów warto spędzić kilka godzin na zapoznanie się z aplikacją. Przetestować ją ręcznie. Znając mechanizmy, którymi testowany program się kieruje, znacznie przyspieszymy i ułatwimy sobie proces pisania testów automatycznych.

2. Sprzątaj po sobie

 

 

Wyobraźmy sobie przykładowy scenariusz testu:

 

  • logowanie do administratora,
  • założenie nowego użytkownika,
  • przetestowanie jego edycji.

 

Czego tu brakuje?

A teraz wykonajmy ten test 150 razy.

 

Tak, testerzy muszą(!) po sobie sprzątać. Przykład użytkownika jest dość niewinny ale często w aplikacji mamy możliwość dodania obrazków lub filmów. My osobiście, osiągnęliśmy punkt krytyczny w którym na serwerze znajdowało się prawie kilkadziesiąt GB efektów naszych testów.

 

3. Twórz testy, które “myślą”

 

Zakładanie z góry, że pewne elementy w aplikacji będą istnieć, w czasie uruchomienia testów, jest dobrym rozwiązaniem. Lecz jedynie na początek, kiedy bardziej zależy nam na przetestowaniu danej funkcjonalności, niż na inteligencji naszych skryptów. Jednak czy powinniśmy zostawiać to w tej postaci?

 

Przyjmijmy, że elementem wymaganym jest konto użytkownika testowego. W przypadku gdy go zabraknie w wyniku zwrócimy porażkę testów, w których logujemy się na to konto. Jedyne co możemy wtedy zrobić to zadać sobie pytanie czy to wina braku konta czy może pojawił się jakiś błąd w aplikacji. Poprawna odpowiedź pojawi się dopiero po zarejestrowaniu użytkownika i włączeniu testów jeszcze raz. Gdzie w tym problem?

 

Musimy sami obsłużyć aplikację i ręcznie stworzyć konto użytkownika oraz uruchomić ponownie skrypty testujące. Przecież to testy automatyczne, one powinny wykonywać takie czynności za nas. Właściwie po to są tworzone. W naszym interesie jest więc zbudowanie testów, które będą umiały reagować na takie sytuacje.

 

W naszej aplikacji do poprawnego zalogowania użytkownika musiały zostać spełnione dwa kryteria: musiał być ustawiony odpowiedni budżet oraz musiało istnieć konto, na które chcemy się zalogować. Przykład metody pomocniczej, która sama reagowała na brak odpowiedniego budżetu i brak użytkownika  testowego możecie zobaczyć na zrzucie poniżej.

 

 

blog1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4. Przesuwaj kursor(?)

 

Podczas pracy z Selenium napotkaliśmy problem, którego rozwiązanie zajęło nam sporo czasu. Mianowicie przy jednym z testów narzędzie to nie zawsze klikało w miejsce, które mu wskazaliśmy. Sam kod sprawdzaliśmy dobre kilkanaście razy o przerabianiu go nie wspominając.

 

Okazało się, że to kursor myszy jest winowajcą. Kiedy znajdował się wewnątrz okna przeglądarki zaburzał działanie testów. Selenium, zanim wykona akcje na jakimś elemencie, zachowuje się podobnie jak my. Odnajduje element, tak jak człowiek ‘najeżdża’ na niego myszą, czego efekt można zaobserwować chwilową zmianą stylu na przykład przycisku. Dlatego gdy nasz kursor znajdował się w oknie przeglądarki Selenium nie mogło kliknąć na ten element.

 

Do rozwiązania tego problemu musieliśmy użyć klasę Cursor pochodzącą z przestrzeni System.Windows.Forms i po prostu wraz z każdym uruchomieniem przeglądarki kursor myszy był przesuwany na górę ekranu gdzie nikomu nie wadził.

 

Przykład kodu przesuwającego wskaźnik myszy możecie zobaczyć na zrzucie poniżej.

 

blog2

 

 

 

 

 

 

 

 

5. Nie zawsze musisz pobierać pliki

 

W wielu aplikacjach istnieje możliwość pobrania plików na dysk. Mogą to być obrazki, filmy, muzyka i tym podobne. Jednak czy przy pisaniu testów sprawdzających możliwość pobrania danego pliku trzeba za każdym razem pobierać go na dysk?

 

To zależy czy chcemy wykonywać na nim jakieś akcje gdy już znajdzie się na naszym komputerze. Jeśli mamy zamiar to robić w takim razie siłą rzeczy musimy go pobrać. W przypadku gdy chcemy tylko przetestować samą możliwość ściągnięcia pliku, możemy to zrobić bez fizycznego pobierania pliku na dysk.

 

W tym celu należy wyszukać na stronie link do pliku, który chcemy pobrać, następnie wysłać do niego zapytanie HTTP i odczytać kod, który dostaniemy w odpowiedzi. Jeśli będzie to kod 200 (OK) to znaczy, że plik można pobrać. Niestety jeśli do pobrania pliku będzie niezbędna aktywna sesja użytkownika,  trzeba wyciągnąć jeszcze “cookie” lub “token” sesji i wraz z nim wykonać zapytanie HTTP.

 

Przykładowy kod sprawdzający czy pliki można pobrać na listingu poniżej.

 

blog3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Przedstawiamy… Page Object Pattern

Testy są same w sobie aplikacją. Kodem pozornie niezależnym od testowanego programu. Pozornie ponieważ jakakolwiek zmiana w testowanej aplikacji musi zostać uwzględniona także w testach. Jak to wygląda? Spójrzmy na kawałek kodu.

 

blog4

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Ale zaraz gdzie w tym kodzie są jakiekolwiek testy? Przecież na pierwszy rzut oka widać, że ta klasa służy tylko do odnajdywania elementów na stronie i udostępnia metodę logowania do konta administratora.

 

To właśnie jest wspomniany w tytule Page Object Pattern. Jest to wzorzec umożliwiający rozdzielenie kodu odpowiedzialnego za wyszukiwanie elementów HTML na stronie i interakcję z nimi, od kodu, którego zadaniem jest testowanie aplikacji. Spójrzmy teraz na kod testu, który używa klasy AdminLoginPage.

 

blog5

 

 

 

 

 

 

 

Nie można się nie zgodzić z tym, że wzorzec ten pozwala na tworzenie naprawdę czytelnych testów. Jakie jeszcze są jego zalety?

 

Na początku rozdziału wspomnieliśmy o tym, że każda zmiana w aplikacji musi mieć swoje odzwierciedlenie w testach. Wyobraźmy sobie teraz, że nie używamy wzorca Page Object Pattern.

 

  • W każdym teście korzystającym z określonego elementu musimy wyszukiwać go na stronie.
  • Elementów może być nawet kilkanaście na każdej ze stron.
  • Powiedzmy, że programista rozwijający aplikację zmienił klasę bądź ID kilku elementów.

 

W ilu miejscach musimy wprowadzić zmiany w testach? Zapewne w wielu. Jednak implementując prezentowany przez nas wzorzec, wyszukujemy każdy element tylko raz, więc każda tego typu zmiana naniesiona zostaje również tylko w jednym miejscu. Nie musimy chyba mówić jakim udogodnieniem jest tak wygodna aktualizacja kodu.

 

W bardziej skomplikowanych projektach, takich jak ten, przy którym było nam dane pracować, nie wyobrażamy sobie pracy bez opisywanego wzorca. Znacząco ułatwił nam on wprowadzanie poprawek do testów, które przestawały działać z powodu zmian w aplikacji. Także tworzony z jego użyciem kod stał się dużo czytelniejszy. Jednak przy mniejszych projektach musicie zastanowić się czy warto inwestować dodatkowy czas poświęcony na jego implementację.

 

Firefox vs Chrome

Na początku wpisu wspomnieliśmy, że korzystaliśmy z obu wymienionych w tytule przeglądarek. Ostatecznie musieliśmy użyć Firefoxa, jednak gdyby decyzja należała do nas, zostalibyśmy przy Chromie.

 

Mozilla jest wolniejsza. Gdy uruchamialiśmy nasze testy korzystając z przeglądarki Google’a czas, w którym się wykonywały wynosił około 11-13 minut. Po zmianie przeglądarki około 20-23 minuty. Wydłużył się więc prawie dwukrotnie.

 

Na czas wykonywania testów można przymknąć oko jeżeli przeglądarka byłaby bardziej przyjazna dla testera. Niestety nie jest. Chrome każdorazowo sam czekał aż strona załaduje się w pełni i dopiero wtedy wykonywał na niej akcje. O Mozilli niestety powiedzieć tego nie można. Skutkowało to tym, że do scenariuszy testowych musieliśmy po każdym przeładowaniu strony dodawać WebDriverWait. Czyli zwyczajnie zmuszać przeglądarkę, żeby poczekała na pełne załadowanie strony. Bez tego akcje zawarte w teście wykonywane były na nieistniejących jeszcze elementach, co powodowało porażkę połowy testów, które na Chromie przechodziły bez zarzutu.

 

Akcja “przeciągnij i upuść” (ang. drag and drop) jest jedną z funkcjonalności oferowanych przez Selenium. Używaliśmy jej w kilku testach pod przeglądarką od Google i działała bez zarzutu. Po przeniesieniu na Firefoxa testy te nagle przestały działać. Okazało się, że jedynym sposobem przetestowania tej funkcjonalności pod Mozillą jest użycie JavaScriptu.

 

Jedyne co wówczas przemawiało na korzyść przeglądarki od Mozilli to wbudowany WebDriver, który aktualizował się razem z przeglądarką. Pozwala to uniknąć problemów (które mogą nigdy się nie zdarzyć) związanych z tym, że korzystać będziemy ze starszego WebDrivera na nowszej przeglądarce.

 

Należy jedynie pamiętać, że przeglądarki oraz ich wsparcie dla Selenium zmieniają się dosyć dynamicznie i w momencie czytania tego tekstu może okazać się, że Selenium najlepiej wspierane jest pod zupełnie inną przeglądarką. Jednak na ten czas, jest to Chrome.

 

Warto pisać testy automatyczne?

Jest to bardzo ciężkie pytanie, na które niestety nie ma prostej i jednoznacznej odpowiedzi. Potocznie mówiąc: “to zależy”.

 

Z naszej perspektywy, jako praktykantów, warto. Pisanie testów jest ciekawym doświadczeniem, które na pewno rozwija umiejętności programistyczne jak i pozwala pracować przy prawdziwym projekcie. Jednocześnie więc pracujemy z innymi ludźmi odpowiedzialnymi za aplikacje ale z drugiej strony nasze braki w wiedzy i umiejętnościach nie odbijają się na projekcie tak mocno jak przy pracy z kodem źródłowym właściwego programu.

 

Jednakże testy automatyczne mają swoje minusy. Przede wszystkim do ich tworzenia trzeba skierować osoby, które mogły by w tym czasie poświęcić się rozwijaniu lub tworzeniu produktu dla klienta (chyba, że stać nas na osoby, które zajmowałyby się tylko testami). Na tym jednak się nie kończy. Raz napisanych testów niestety nie można pozostawić samym sobie po ich utworzeniu. Trzeba je w dalszym ciągu wspierać wraz z tym jak aplikacja się rozwija. W związku z tym mamy jakby dwa produkty rozwijane równocześnie.

 

Wiemy już, że testy trzeba stale ulepszać wraz z rozwojem aplikacji, co może niekiedy pochłaniać dodatkowy czas i pieniądze. Niesprawiedliwie byłoby jednak na tym zakończyć.

 

Należy wziąć pod uwagę wartość wniesioną podczas samego procesu tworzenia testów. Podczas naszych praktyk, z programistą odpowiedzialnym za rozwój właściwej aplikacji, siedzieliśmy “plecy w plecy”, dzięki czemu już w momencie projektowania scenariusza testowego byliśmy w stanie zawiadomić go o ewentualnych błędach na stronie.

 

W sumie wszystkich niepowielających się testów napisaliśmy ok. 90. Każdy sprawdzający inną funkcjonalność strony. Ile zajęło właściwe testowanie?

 

blog6

 

 

 

 

 

 

 

 

 

Zajęło to łącznie ok. 21 minut. Przy szybkości komputera który nie musi szukać wzrokiem elementu na stronie, ani przesuwać kursorem aby w niego kliknąć. Nie musi także uważać aby nie wpisać nieprawidłowych danych, ponieważ my jako twórcy testów przewidzieliśmy taką ewentualność.

 

Proszę sobie wyobrazić ile zajęłoby to jednej a nawet dwóm osobom. Należy także dodać, że testy zostały przez nas ukończone w ok. 2,5 tygodnia. Można więc śmiało założyć, że jest to szybki i dokładny sposób sprawdzenia poprawności produktu.

 

Co byśmy zmienili pisząc testy po raz drugi?

Patrząc na to z perspektywy czasu (posta piszemy ponad miesiąc po stworzeniu testów) zmienilibyśmy naprawdę niewiele. Uważamy, że napisany przez nas kod jest czytelny i nie powinno być problemów z jego utrzymaniem. Oczywiście o ile na bieżąco testy są poprawiane razem z nowościami wprowadzanymi w aplikacji. W przeciwnym wypadku nawet najlepiej napisany kod będzie trudny do ogarnięcia.

 

Zmiany jakie wprowadzilibyśmy na chwilę obecną są raczej ukierunkowane na większe uporządkowanie projektu i dołączenie metod rozszerzających, które umożliwiają bardziej naturalne sprawdzanie wyników testów.

 

Aby osiągnąć pierwsze z wyżej wymienionych założeń, wprowadzilibyśmy podział testów na podstawowe, które testują tylko ogólne reakcje strony. Na przykład czy link przekierowuje w odpowiednie miejsce lub czy wysłanie formularza powoduje wyświetlenie komunikatu o (nie)powodzeniu. Oraz na takie, które testują logiczny aspekt czyli jakie rezultaty powinny wywołać określone akcje na przykład czy usunięcie użytkownika powoduje zmianę w ilości użytkowników wyświetlanych w odpowiedniej zakładce i tym podobne.

 

Metody rozszerzające, o których wspominaliśmy nazywają się Fluent Assertions. Mówiliśmy o tym, że umożliwiają one bardziej naturalne sprawdzanie wyników testów. Być może są one przydatniejsze do testów jednostkowych/integracyjnych gdzie wyniki potrafią być bardziej skomplikowane niż w testach automatycznych jednak mimo wszystko polecamy zapoznać się z nimi samemu. Są dużo bardziej intuicyjne i czytelne niż te wbudowane w NUnit. Poniżej prosty przykład sprawdzający wynik testu jednostkowego z ich użyciem.

 

blog7

 

 

 

 

 

Podsumowanie

Mamy nadzieję, że nasz wpis pomógł odrobinę osobom, które tak jak my nigdy wcześniej nie miały styczności z testami automatycznymi. Gdybyśmy my na samym początku znaleźli tego typu post na pewno zaoszczędziłby nam on sporo czasu. Jednocześnie przepraszamy wszystkie osoby bardziej obeznane w tym temacie za wszystkie błędy, których na naszym poziomie niestety nie widzimy.

 

Staraliśmy się przekazać nasze (skromne) doświadczenia w jak najlepszy sposób. Jednak jest to nasz pierwszy publikowany tekst i mamy świadomość, że może nie być on idealny. W związku z tym zapraszamy do sprawdzenia naszego wpisu (zarówno pod względem merytorycznym jak i naszych umiejętności w posługiwaniu się ojczystym językiem) i podzielenia się wynikami w komentarzach. Może to właśnie Ty znajdziesz błąd, który będziemy musieli poprawić? 🙂

 

blog8

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Autorami wpisu są:

Mateusz Kłosiński

Adrian Jagodziński

Studenci Uniwersytu Technologiczno-Przyrodniczego w Bydgoszczy, kierunek informatyka stosowana.

 

 



Opublokowano: 13-10-2016 |