Przyszedł czas na zdobycie wierzchołka testerskiej piramidy. Zabierzemy się za testy e2e (end-to-end). W teorii to najlepszy sposób na upewnienie się, że aplikacja działa jak należy. Możliwość dokładnego odtworzenia zachowań użytkownika w środowisku przeglądarki jest nadzwyczaj obiecująca. Niestety, praktyka szybko obnaża ukryte słabości tego podejścia do testowania. Testy e2e są czasochłonne i niezwykle wrażliwe na zmiany w aplikacji. Czy to oznacza, że lepiej byłoby dać sobie z nimi spokój? Bynajmniej. Możemy mieć z nich nie lada użytek, wystarczy zachować umiar przy tworzeniu przypadków testowych i wyposażyć się w dobre narzędzia. Właśnie o jednym z takich narzędzi będzie mowa w dzisiejszym wpisie. Zobaczymy, jak sprawdza się w akcji zyskujący popularność framework cypress.io.
Disclaimer
W myśl zasady DRY: nie będę się zajmował wyjaśnianiem terminologii i omawianiem procesu planowania przypadków testowych. Szeroko omówiłem tę tematykę w poprzednich wpisach z serii Testowanie komponentów React. Jeżeli nie miałeś okazji ich przeczytać, zachęcam do nadrobienia zaległości.
New kid on the block
Cypress pozwala na testowanie każdej aplikacji uruchamianej w przeglądarce. Jest to rozwiązanie kompletne, zawiera wszystko, co potrzebne do pisania i uruchamiania testów.
Ponadto, mamy do dyspozycji następujące funkcjonalności ułatwiające pracę: snapshoty, hot reloading testów, mechanizm oczekiwania i kilkukrotnego wyszukiwania węzłów DOM (testy kodu asynchronicznego nigdy nie były tak łatwe!). To wszystko składa się w całkiem zgrabną całość.
Zdecydowałem się na wybór tego narzędzia zamiast popularniejszcych technologii bazujących na Selenium (np. Nightwatch.js) ze względu na niski próg wejścia i świetną dokumentację. No dobra, nie ma co ukrywać, nowinkowa chciwość również maczała w tym palce.
Jeżeli to Twoja pierwsza styczność z cypressem, to masz do dyspozycji poradnik w formie tekstowej oraz darmowy kurs video. Z pomocą tych materiałów poznasz sam framework oraz, co ważniejsze, wiele zasad poprawnego pisania testów e2e. Gorąco polecam.
Ja skupię się przede wszystkim na prezentacji podstaw korzystania z cypressa. Po przeczytaniu tego artykułu będziesz gotowy do samodzielnego testowania kluczowych elementów swojego projektu.
Tradycyjnie naszym królikiem doświadczalnym będzie projekt crypto-tracker (demo znajdziesz tutaj).
Zapraszamy cypressa na pokład
Bez niespodzianek zaczynamy od instalacji pakietu:
A następnie dodajemy do package.json nowy skrypt npm:
W przeciwieństwie do skryptów, z których korzystaliśmy przy pracy z Jest i Enzyme, ten nie spowoduje uruchomienia testów. Jego zadaniem jest utworzenie folderu /cypress
zawierającego domyślną strukturę plików oraz uruchomienie graficznego panelu zarządzania frameworkiem.
Struktura plików
Przyjrzyjmy się folderom dodanym do projektu:
fixtures
to odpowiednik __mocks__ znanego nam z Jest. To miejsce do przechowywania sztucznych danych wykorzystywanych jako atrapy w testach.integration
to folder, w którym będziemy przechowywali specyfikacje testów.plugins
to miejsce na pliki wtyczek. Ich wykorzystanie jest niezbędne do obsługi TypeScripta czy nowości z ES7+.support
zawiera plikindex.js
, wykonywany przed każdym testem. Przypomina trochęsetupTests.js
z Jest. Możemy w nim zadeklarować zmienne globalne udostępniane we wszystkich przypadkach testowych.
Centrum dowodzenia
Wszystkie przeprowadzane przez nas testy e2e zaczną i skończą się w graficznym panelu zarządzania.
Jego obsługa jest dziecinnie prosta. Kliknięcie w nazwę testu powoduje uruchomienie go (suprise no. 1). Mamy również możliwość odpalenia wszystkich testów za pomocą „Run all specs” (suprise no. 2). Zaraz obok znajduje się dropdown, w którym wybieramy testowaną przeglądarkę. Niestety, wybór jest czysto symboliczny. Do dyspozycji mamy jedynie Chrome… To niewątpliwie największa wada tego narzędzia. Wsparcie dla pozostałych przeglądarek jest w drodze, postęp prac można śledzić w tym wątku.
Dla poszukiwaczy niesamowitych wrażeń, w zakładce „Runs” możecie nagrywać swoje testerskie wyczyny.
Proces pisania testów
Smoke test na rozgrzewkę
Sprawdzimy, czy crypto-tracker uruchamia się jak należy pod domyślnym adresem url serwera developerskiego.
W tym celu utworzymy nowy przypadek testowy pod ścieżką cypress/integration/app-init.spec.js
.
Dzięki hot reloadingowi, test case od razu pojawi się w naszym panelu zarządzania, więc możemy go odpalić…
Woops, falstart. Na szczęście komunikat błędu nie pozostawia wątpliwości co do natury naszego problemu. Zapomniałem o jednej istotnej sprawie. Przed rozpoczęciem testowania, wypadałoby uruchomić wspomniany serwer, którego URL wskazałem w cy.visit()
. Wklepanie yarn start
do terminala załatwia sprawę.
Jak widzisz, test wykonał się w okrojonej wersji Chrome. Po lewej widzisz panel testów, a po prawej podgląd aplikacji.
Właściwe przypadki testowe będą odwzorowaniem testów integracyjnych, które przygotowałem w ramach poprzedniego wpisu: Jak pisać testy integracyjne w React z Jest i Enzyme. crypto-tracker
jest prostą aplikacją, co nie pozostawiło mi wiele pola do manewrów ;). W swoich projektach stosuj następującą heurystykę: testuj wyłącznie najistotniejsze scenariusze wykorzystania aplikacji i unikaj sprawdzania szczegółów podatnych na częste zmiany (np. stylowanie elementów).
Liczymy kryptowaluty
Dodajmy do testu pierwszą asercję. Wypadałoby sprawdzić, czy faktycznie udało się pobrać z serwera dane kryptowalut i wyświetlić je na ekranie.
Pierw wywołujemy metodę cy.server(), która pozwali nam obsługę komunikacji klient-serwer.
Kontrolę nad konkretnymi zapytaniami umożliwia nam metoda cy.route(). Za jej pomocą nadałem zapytaniu GET alias fetchCryptos
.
Aliasy pozwalają na odwoływanie się do zapytania w kolejnych metodach. Ja skorzystałem z tej możliwości przy wywołaniu cy.wait().
Dzięki temu cypress zaczeka z asercją do momentu otrzymania odpowiedzi z serwera (lub upłynięcia czasu wskazanego w timeout
).
Jak widzisz, zrezygnowałem z mockowania na rzecz wykonania prawdziwego zapytania do serwera.
Herezja? To zależy.
Testy e2e będą uruchamiane dużo rzadziej niż testy jednostkowe i integracyjne, w których dbaliśmy o mockowanie wszystkich zależności. Dzięki temu możemy zadać sobie pytanie: czy kosztem niewielkiej inwestycji czasu zyskamy pewność co do poprawnej integracji z serwerem?
W przypadku crypto-trackera, gdzie pobieram dane tylko raz, podczas inicjalizacji aplikacji, zdecydowałem się na zrezygnowanie z mockowania.
Oczywiście w aplikacjach, które wykonują tych zapytań więcej, mocki to nadal „way to go”. Mockowanie w cypress jest proste, szczegóły poznasz tutaj.
Jeżeli nie jesteś pewien, jak powinieneś podejść do tego tematu, skorzystaj z analizy plusów i minusów stubowania dostępnej tutaj.
Więcej na temat tego, jak radzić sobie z AJAXem znajdziesz w sekcji dokumentacji Network requests.
Asercje
Wróćmy do przypadku testowego. Jest jeszcze kilka rzeczy, które mogą Cię zastanawiać. Skąd taki selektor, za pomocą którego odwołałem się do węzłów DOM reprezentujących komponent Coin?
Samo dodanie selektora do kontenera komponentu było niezbędne. Cypress to nie Enzyme, nie był tworzony wyłącznie z myślą o Reactcie. Nie mamy możliwości bezpośredniego wyszukiwania komponentów, musimy radzić sobie z tym, co trafia do DOM.
Zamiast dodawać zbędne klasy CSS, skorzystałem z rozwiązania zaproponowanego w kolejnym jasnym punkcie dokumentacji cypressa czyli liście najlepszych praktyk.
Jak słusznie wskazują twórcy cypressa, klasy CSS mają to do siebie, że są podatne na zmiany. Szkoda, żeby refaktoryzacja czy zmiana metodyki była powodem do dodatkowej pracy przy testach. Lepiej wydzielić sobie oddzielny atrybut, który będzie zadeklarowany na wyłączność testów. Stąd właśnie data-cy=coin
.
Jeżeli czytałeś mój wpis o testach jednostkowych, na pewno zauważyłeś, że oprócz selektora sama asercja wygląda trochę inaczej. Cypress wykorzystuje bibliotekę asercji Chai, a my do tej pory korzystaliśmy z matcherów Jest. Porównajmy je ze sobą na tym samym przykładzie:
Wystarczy chwilę przyjrzeć się tym dwóm linijkom, żeby stwierdzić, że różnice są tak naprawdę kosmetyczne.
Jedyne, czego nam trzeba, to zapoznać się z metodą should oraz listą dostępnych matcherów asercji.
Podróże w czasie z snapshotami
Zobaczymy w praktyce jeden z ciekawszych feature’ów cypressa czyli snapshoty. Dzięki nim możemy cofać się w czasie do kluczowych momentów wykonywanego przypadku testowego.
Po kliknięciu w (XHR) GET 200 możemy cofnąć się do momentu, w którym doszło do wywołania zapytania GET. Rzecz niesamowicie przydatna przy debugowaniu.
Nie trzeba wiele
Pozostałe trzy przypadki testowe zajęły się weryfikowaniem integracji paska wyszukiwania i listy Coinów. ich kod znajdziesz tutaj. Każdy z nich zamyka się w 10 linijkach kodu. Jedyne dodatkowe metody, z którymi musiałem się zapoznać to type() i clear().
Świetny dowód na to, że nie trzeba wiele zainwestować, żeby wyciągnąć wartość z testów e2e w Cypress.