Co wziąć pod uwagę podczas pisania testów E2E #frontend@twiliosendgrid

Opublikowany: 2020-09-19

W Twilio SendGrid piszemy testy end-to-end (E2E) pod koniec nowej funkcji lub cyklu opracowywania strony, aby upewnić się, że wszystkie części są połączone i działają poprawnie między frontendem a backendem z perspektywy użytkownika końcowego.

Eksperymentowaliśmy z różnymi frameworkami i bibliotekami do testowania E2E, takimi jak nasz własny, niestandardowy framework Ruby Selenium, WebdriverIO, a przede wszystkim Cypress przez ponad dwa lata, jak podkreślono w części pierwszej i drugiej serii postów na blogu dokumentującej naszą migrację we wszystkich rozwiązania. Niezależnie od użytego frameworka lub biblioteki, zadaliśmy te same pytania o to, jakie funkcje możemy zautomatyzować i dla których pisać testy E2E. Po określeniu, które funkcje możemy przetestować, zauważyliśmy, że stosujemy tę samą ogólną strategię przy pisaniu i konfigurowaniu testów.

Ten wpis na blogu nie wymaga żadnej wcześniejszej wiedzy na temat pisania testów E2E w określonej bibliotece lub frameworku, ale pomaga, jeśli widziałeś aplikacje internetowe i zastanawiałeś się, jak najlepiej zautomatyzować działanie przeglądarki, aby strony działały poprawnie. Naszym celem jest pokazanie, jak myśleć o testach E2E, abyś mógł zastosować te pytania i ogólną strategię pisania testów w dowolnym wybranym przez siebie frameworku.

Pytania, które należy zadać, jeśli możesz zautomatyzować test E2E

Jeśli chodzi o pisanie testów E2E, musimy upewnić się, że przepływy na stronach, które testujemy w naszej aplikacji, spełniają określone kryteria. Przyjrzyjmy się niektórym z pytań wysokiego poziomu, które zadajemy sobie, aby określić, czy test E2E można zautomatyzować.

1. Czy możliwe jest zresetowanie danych użytkownika z powrotem do określonego stanu przed każdym testem za pomocą pewnego niezawodnego sposobu, takiego jak API? Jeśli nie ma możliwości niezawodnego przywrócenia użytkownika do pożądanego stanu, nie można go zautomatyzować i oczekiwać, że będzie on uruchamiany w ramach testów blokowania przed wdrożeniem. Jest to również antywzorzec i zwykle niedeterministyczny powrót użytkownika do określonego stanu za pośrednictwem interfejsu użytkownika, ponieważ jest to powolny, a automatyzacja kroków w interfejsie użytkownika jest już wystarczająco niepewna. Bardziej niezawodne jest wykonywanie wywołań API w celu zresetowania stanu użytkownika bez konieczności otwierania strony w przeglądarce. Inną alternatywą, jeśli masz usługę, która istnieje, jest utworzenie nowych użytkowników przed każdym testem z odpowiednimi danymi. Dopóki zresetujemy utrwalonego użytkownika lub utworzymy użytkownika przed każdym testem, możemy skupić się na częściach, które testujemy na stronie.

2. Czy mamy kontrolę nad funkcją, interfejsem API lub systemem, który zamierzamy przetestować? Jeśli jest to usługa innej firmy, na której polegasz w zakresie rozliczeń lub jakiejkolwiek innej funkcji, czy istnieje sposób, aby je wykpić lub sprawić, by działała deterministycznie z określonymi wartościami? Chcesz uzyskać jak największą kontrolę nad testem, aby zmniejszyć łuszczenie. Możesz utworzyć dedykowanych użytkowników testowych z izolowanymi zasobami lub danymi na przebieg testu, tak aby nic innego nie miało na nie wpływu.

3. Czy sama usługa lub funkcja jest wystarczająco spójna, aby działać w rozsądnym czasie? Często może być konieczne zaimplementowanie sondowania lub poczekanie na przetworzenie określonych danych i wprowadzenie ich do bazy danych (np. wolniejsze aktualizacje asynchroniczne i wyzwalane zdarzenia e-mail). Jeśli te usługi często występują w rozsądnym i niezawodnym przedziale czasowym, możesz ustawić odpowiednie limity czasu odpytywania podczas oczekiwania na pojawienie się określonych elementów DOM lub aktualizację danych.

4. Czy możemy wybrać elementy, z którymi musimy wejść w interakcję na stronie? Masz do czynienia z elementami iframe lub generowanymi elementami, nad którymi nie masz kontroli i których nie możesz zmienić? Aby wchodzić w interakcje z elementami na stronie, możesz dodać bardziej szczegółowe selektory, takie jak atrybuty „data-hook” lub „data-testid”, zamiast wybierać identyfikatory lub nazwy klas. Identyfikatory i nazwy klas są bardziej podatne na zmiany, ponieważ są powszechnie kojarzone ze stylami. Wyobraź sobie, że w inny sposób próbujesz wybrać zaszyfrowane nazwy klas lub identyfikatory ze styled-components lub modułów CSS. W przypadku elementów generowanych przez strony trzecie lub bibliotek komponentów typu open source, takich jak React-select, możesz owinąć te elementy elementem nadrzędnym z atrybutem `data-hook` i wybrać elementy potomne poniżej. Aby poradzić sobie z ramkami iframe, stworzyliśmy niestandardowe polecenia, aby wyodrębnić elementy DOM, które musimy potwierdzić i wykonać, co przedstawimy później.

Jest więcej rozważań do rozważenia, ale wszystko sprowadza się do jednego pytania: czy możemy powtórzyć ten test E2E w sposób spójny i terminowy i osiągnąć te same wyniki?

Ogólna strategia pisania testów E2E

1. Znajdź przypadki testowe o wysokiej wartości, które możemy zautomatyzować . Niektóre przykłady obejmują testy szczęśliwej ścieżki obejmujące większość przepływu funkcji: wykonywanie operacji CRUD za pośrednictwem interfejsu użytkownika w celu uzyskania informacji o profilu użytkownika, filtrowanie tabeli w celu dopasowania wyników z określonymi danymi, tworzenie wpisu lub konfigurowanie klucza interfejsu API. Jednak inne skrajne przypadki i obsługa błędów mogą być lepsze, aby uwzględnić je w testach jednostkowych i integracyjnych. Przeprowadź go przez pytania, które wymieniliśmy w poprzedniej sekcji, aby skrócić listę przypadków testowych.

2. Zastanów się, jak powtórzyć te testy, konfigurując lub usuwając interfejs API tak często, jak to możliwe. Aby uzyskać wartościowe, zautomatyzowane przypadki testowe, zacznij zwracać uwagę na to, co należy skonfigurować za pomocą interfejsu API. Niektóre przykłady to umieszczanie w użytkowniku właściwych danych, jeśli użytkownik nie ma wystarczającej ilości danych, które można filtrować do podziału na strony, jeśli dane użytkownika wygasają w ciągu 30 dni lub jeśli musimy ewentualnie usunąć niektóre dane pozostałe po udanych lub niekompletnych testy przed ponownym rozpoczęciem bieżącego testu. Testy powinny być w stanie uruchomić się i ustawić w tym samym powtarzalnym stanie, niezależnie od tego, czy ostatni test zakończył się pomyślnie, czy nie.

Ważne jest, aby pomyśleć: jak mogę zresetować dane tego użytkownika z powrotem do punktu początkowego, aby móc przetestować tylko tę część funkcji, którą chcę?

Na przykład, jeśli chcesz przetestować możliwość dodania posta przez użytkownika, aby ostatecznie pojawił się na liście postów użytkownika, post musi zostać najpierw usunięty.

3. Wejdź w buty klienta i śledź kroki interfejsu użytkownika potrzebne do pełnego zakończenia przepływu funkcji. Zapisz kroki dla klienta, aby wykonać pełny przepływ lub działanie. Śledź, co użytkownik powinien, a czego nie powinien widzieć lub wchodzić w interakcje po każdym kroku. Po drodze będziemy przeprowadzać kontrole poprawności i asercje, aby upewnić się, że użytkownicy napotykają właściwą sekwencję zdarzeń dla swoich działań. Następnie przetłumaczymy testy poprawności na automatyczne polecenia i potwierdzenia.

4. Utrzymuj zmiany i automatyzuj przepływy, dodając określone selektory i implementując obiekty strony (lub dowolny inny rodzaj opakowania). Przejrzyj te kroki, które zapisałeś, aby dowiedzieć się, jak manewrować i przejść przez przepływ funkcji. Dodaj bardziej szczegółowe selektory, takie jak atrybuty „data-hook”, do elementów, z którymi użytkownik wchodził w interakcję, takich jak przyciski, modalności, dane wejściowe, wiersze tabeli, alerty i karty. Jeśli wolisz, możesz tworzyć obiekty strony, opakowania lub pliki pomocnicze z odniesieniami do tych elementów za pomocą dodanych selektorów. Następnie możesz zaimplementować funkcje wielokrotnego użytku, aby wchodzić w interakcje z aktywnymi elementami strony.

5. Przetłumacz kroki użytkownika, które zapisałeś, na testy Cypress za pomocą utworzonych przez siebie pomocników. W testach zwykle logujemy się do użytkownika za pośrednictwem interfejsu API i zachowujemy plik cookie sesji przed każdym uruchomieniem przypadku testowego, aby pozostać zalogowanym. Następnie konfigurujemy lub usuwamy dane użytkownika za pośrednictwem interfejsu API, aby uzyskać spójny punkt początkowy. Gdy wszystko jest gotowe, odwiedzamy stronę, którą będziemy testować bezpośrednio pod kątem naszej funkcji. Kontynuujemy wykonywanie kroków dla przepływu, takich jak tworzenie, aktualizacja lub usuwanie przepływu, stwierdzając, co powinno się wydarzyć lub być widoczne na stronie po drodze. Aby przyspieszyć testy i zmniejszyć niestabilność, unikaj resetowania lub budowania stanu za pomocą interfejsu użytkownika i omijaj takie rzeczy, jak logowanie się przez stronę logowania lub usuwanie rzeczy za pomocą interfejsu użytkownika, aby skupić się na częściach, które chcesz przetestować. Upewnij się, że zawsze wykonujesz te części w haczykach `before` lub `beforeEach`. W przeciwnym razie, jeśli użyłeś zaczepów `after` lub `afterEach`, testy mogą zakończyć się niepowodzeniem, co spowoduje, że twoje kroki czyszczenia nigdy nie zostaną uruchomione, a kolejne testy mogą zakończyć się niepowodzeniem.

6. Uderz młotkiem i wytnij testową łuskość. Po zaimplementowaniu testów i kilkukrotnym ich przejściu lokalnie, kuszące jest skonfigurowanie żądania ściągnięcia, natychmiastowe scalenie go i uruchomienie testów zgodnie z harmonogramem z resztą zestawu testów lub wyzwolenie ich w krokach wdrażania. Zanim to zrobisz:

    1. Najpierw spróbuj pozostawić użytkownika w różnych stanach i sprawdź, czy testy nadal przechodzą pomyślnie, aby upewnić się, że masz odpowiednie kroki konfiguracyjne.
    2. Następnie zbadaj uruchamianie testów równolegle, gdy zostaną wyzwolone podczas jednego z przepływów wdrażania. Pozwala to sprawdzić, czy ci sami użytkownicy depczą zasoby i czy występują jakieś warunki wyścigu.
    3. Następnie obserwuj, jak Twoje testy działają w trybie bezgłowym w kontenerze Docker, aby sprawdzić, czy konieczne może być zwiększenie limitów czasu lub dostosowanie selektorów.

Celem jest sprawdzenie, jak Twoje testy zachowują się w powtarzających się testach w różnych warunkach i uczynienie ich tak stabilnymi i spójnymi, jak to tylko możliwe, dzięki czemu spędzamy mniej czasu na powracaniu do testów i skupiamy się bardziej na wyłapywaniu rzeczywistych błędów w naszych środowiskach.

Oto przykładowy układ szablonu testu Cypress, w którym utworzyliśmy polecenie globalnego wsparcia logowania o nazwie `cy.login(nazwa użytkownika, hasło).` Ustawiamy plik cookie jawnie i zachowujemy go przed każdym testem, abyśmy mogli pozostać zalogowani i przejść bezpośrednio do stron, które testujemy. Przeprowadzamy również konfigurację lub usuwamy interfejs API i za każdym razem omijamy stronę logowania, jak pokazano poniżej.

Kończące myśli

Oprócz porównania, które rozwiązanie E2E jest najlepsze w użyciu, ważne jest również przyjęcie odpowiedniego nastawienia do testowania E2E. Bardzo ważne jest, aby najpierw zadać pytania dotyczące tego, czy funkcja, którą chcesz przetestować, spełnia wymagania, które mają być zautomatyzowane. Powinny istnieć sposoby na niezawodne zresetowanie użytkownika lub danych do określonego stanu (np. za pomocą interfejsu API), aby można było skupić się na tym, co próbujesz zweryfikować.

Jeśli nie ma niezawodnego sposobu na zresetowanie użytkownika lub danych do właściwego punktu początkowego, należy przyjrzeć się tworzeniu narzędzi i interfejsów API do tworzenia użytkowników z określonymi konfiguracjami. Możesz również rozważyć wyśmiewanie rzeczy, które możesz kontrolować, aby testy były jak najbardziej stabilne i spójne. W przeciwnym razie powinieneś rozważyć wartość i kompromisy ze swoim zespołem. Czy tę funkcję należy pozostawić testom jednostkowym lub ręcznym testom regresji, gdy wprowadzane są nowe zmiany w kodzie?

W przypadku tych funkcji, które można zautomatyzować w testach E2E, często najcenniejsze jest zapoznanie się z głównymi przepływami szczęśliwych ścieżek użytkowników. Po raz kolejny spraw, aby rzeczy były powtarzalne w sposób terminowy i spójny oraz wyeliminuj niestabilność podczas pisania testów E2E z dowolnym frameworkiem lub biblioteką, której potrzebujesz.

Aby uzyskać więcej informacji na temat testów Cypress E2E, zapoznaj się z następującymi zasobami:

  • 1000 stóp Przegląd pisania testów na cyprys
  • TypeScript Wszystkie rzeczy w twoich testach cyprysowych
  • Radzenie sobie z przepływami wiadomości e-mail w testach cyprysowych
  • Pomysły na konfigurację, organizację i konsolidację testów cyprysowych
  • Integracja testów Cypress z Docker, Buildkite i CICD