Wprowadzenie testów to najlepszy sposób na podniesienie jakości i niezawodności tworzonego przez Ciebie oprogramowania. Jak pokazała przeprowadzona przeze mnie ankieta, czytelnicy bloga świetnie zdają sobie sprawę. Mimo najlepszych chęci, pierwsze kroki w świecie testowania mogą być przytłaczające. Terminologia, konfiguracja środowiska, o samym pisaniu testów nie mówiąc. Tym razem udowadniam, że to żadne rocket science. Po przeczytaniu tego wpisu będziesz mógł zabrać się za testowanie komponentów React.
Czego dowiesz się z lektury tego artykułu?
- Czym są asercje?
- Co różni testy jednostkowe, integracyjne i end-to-end?
- Piramida testowania czyli skuteczny rozkład zasobów
- Jak utrwalić sobie tyle teorii?
- Jakie narzędzia wykorzystuje się do testowania komponentów?
- Jak skonfigurować te narzędzia w aplikacjach utworzonych za pomocą create-react-app?
- Co padnie ofiarą moich pierwszych testów?
Testerski żargon
Z myślą o osobach, które dopiero zaczynają swoją przygodę z testowaniem, przygotowałem krótkie objaśnienie kluczowych pojęć.
Asercja (ang. assertion)
To założenie logiczne, które stanowi sedno każdego testu. Takie założenie sprawdza, czy kod działa w oczekiwany przez nas sposób. Aby nie utonąć w abstrakcji, zobaczmy, jak wygląda przykładowa asercja:
Recepta na sukces w każdym rodzaju testów to właśnie odpowiednia ilość poprawnie skonstruowanych asercji. Cała sztuka testowania polega na podjęciu decyzji, co należy zweryfikować, a co pominąć. Jak to w programistycznym rzemiośle bywa, najlepszym sposobem na wyrobienie sobie takiej intuicji jest praktyka, którą zajmiemy się w kolejnych wpisach.
Testy jednostkowe (ang. unit tests)
Polegają na weryfikowaniu działania najmniejszych części składowych aplikacji. Operujemy bezpośrednio na kodzie źródłowym, mamy pełny wgląd w całą mechanikę jednostki. Izolujemy tę część składową od reszty systemu, koncentrujemy się wyłącznie na niej, bez uwzględnienia współpracy z resztą systemu. Wszystkie zewnętrzne usługi, takie jak pobieranie danych z bazy, zastępuje się mockami czyli atrapami, które imitują ich działanie.
Czym tak naprawdę jest ta cała „najmniejsza część składowa”? TO ZALEŻY (czyli ulubiona odpowiedź każdego początkującego ;)). W vanilla JS są to funkcje/obiekty/klasy, w React to oczywiście komponenty. Z uwagi na propsy, event handlery, wyświetlanie warunkowe i inne kluczowe zagadnienia z Reacta, o których możesz poczytać tutaj, nie będziemy narzekali na brak pracy przy testach jednostkowych.
Testy integracyjne (ang. integration tests)
Sprawdzają współpracę pomiędzy jednostkami, które składają się na określoną usługę/moduł aplikacji. Dobrym przykładem testu integracyjnego byłoby wprowadzenie frazy w komponencie Searchbar
i sprawdzenie, czy komponent CoinList
zwrócił listę wyników.
Testy end-to-end
Skupiają się na sprawdzaniu działania aplikacji/modułu z perspektywy użytkownika. Traktujemy aplikację jako czarną skrzynkę i odtwarzamy scenariusz jej wykorzystania w docelowym środowisku (w przypadku Reacta będzie to przeglądarka).
Na przykładzie trackera kryptowalut: użytkownik uruchamia aplikację (asercja sprawdzająca czy interfejs się załadował), czeka kilka sekund na załadowanie listy kryptowalut (asercja weryfikująca zawartość listy), wyszukuje kryptowalutę w oparciu o jej pełną nazwę lub akronim (asercja sprawdzająca zawartość wyszukiwarki i listy), po czym wraca do pełnej listy (asercja sprawdzająca zawartość listy).
Piramida testowania
Będziemy pisali testy zgodnie z piramidą testowania:
Wielkość każdego z poziomów piramidy odzwierciedla ilość testów danego rodzaju.
Jak widać, skupimy się przede wszystkim na testach jednostkowych. Pewność, że każdy komponent w izolacji spełnia postawione mu zadania, to solidny fundament.
Jednak niezawodny interfejs to coś więcej niż suma składających się na nie wyizolowanych komponentów. To poprawna współpraca pomiędzy nimi jest najważniejsza dla działania aplikacji. Stąd testy integracyjne, chociaż będzie ich trochę mniej, są dla nas równie istotne jak testy jednostkowe.
Dlaczego testy end-to-end są na samym szczycie? Przecież są świetne w wykrywaniu bugów, na które użytkownik może natknąć się podczas swojej codziennej pracy z systemem. Niestety, mają dwie zasadnicze wady. Po pierwsze, są bardzo kruche. Małe zmiany w interfejsie i cały test się sypie, co wiąże się z wysokimi kosztami utrzymania. Ponadto, w porównaniu z testami jednostkowymi są piekielnie wolne.
Jak utrwalić sobie tyle teorii?
Aby swobodnie zajmować się testowaniem, warto sobie utrwalić tę terminologię. Natkniesz się na nią dosłownie wszędzie, w każdej rozmowie, artykule, książce związanej z tą tematyką.
Najskuteczniejszym sposobem – jaki poznałem do tej pory – na szybkie utrwalanie teorii jest metoda Anki. Aby dowiedzieć się, jak korzystać z tego świetnego narzędzia, zerknij do wpisu Anki, czyli jak zapamiętuję WSZYSTKO czego się uczę.
Narzędzia do testowania
Na pełne środowisko testowe aplikacji React składa się: struktura testów, matchery asercji, wyświetlanie wyników, mocki, raporty pokrycia, środowisko imitujące przeglądarkę.
Aby zapewnić tak dużą liczbę funkcjonalności, często korzysta się z kombinacji kilku narzędzi. Aby ograniczyć konfiguracyjne szaleństwo, ograniczyłem tę liczbę do niezbędnego minimum. Jest szansa, że dzięki temu skupimy się na pisaniu testów zamiast oddawać się wątpliwej przyjemności prowadzenia walki ze środowiskiem ;).
Jest
Głównym filarem całego środowiska będzie Jest
, framework z stajni Facebooka. W aplikacjach utworzonych za pomocą create-react-app jest on zapakowany, skonfigurowany i gotowy do działania. Czego chcieć więcej.
Jest
zapewnia wszystkie z kluczowych funkcjonalności, na które składa się środowisko testowe. Dzięki temu możemy od razu zabierać się za pisanie testów jednostkowych i integracyjnych.
Więcej na temat Jest
dowiesz się z przystępnie napisanej dokumentacji. Warto zajrzeć.
Alternatywy dla Jest
: Mocha + Chai (popularny wariant w ekosystemie Reacta) oraz Jasmine (na której Jest
był wzorowany).
Enzyme
Enzyme
to biblioteka przygotowana przez Airbnb specjalnie z myślą o testowaniu komponentów React. Ułatwia renderowanie, przeszukiwanie i tworzenie asercji na komponentach.
Enzyme
renderuje komponent i zapewnia nam intuicyjne API, które pozwala sprawdzić, co udało się wyrenderować. Jeżeli zerkniemy „pod maskę” Enzyme
, natkniemy się na cheeriojs
, małą bibliotekę implementującą funkcjonalności jQuery Core
. Stąd przeszukiwanie komponentów z Enzyme
do złudzenia przypomina przeszukiwanie DOM z jQuery
, z czym na pewno miałeś już styczność.
Enzyme
daje nam trzy możliwości renderowania:
- shallow -renderujemy wyłącznie wskazany komponent, bez dzieci
- full -renderujemy komponent i wszystkie jego dzieci
- static – renderujemy statyczny kod HTML
Shallow i static rendering nie wymagają korzystania z DOM, dzięki czemu testy tego typu wykonują się naprawdę szybko. Za to full rendering wymaga dostarczenia pełnego DOM API, za które posłuży nam jsdom zapakowane razem z Jest
.
Nasze testy jednostkowe będą korzystały przede wszystkim z shallow renderingu, podczas gdy full rendering przyda się przy testach integracyjnych.
Aby dodać Enzyme
do środowiska testowego, musimy zainstalować dwa pakiety: enzyme
oraz enzyme-adapter-react-16
. Enzyme
korzysta z adapterów do zapewnienia kompatybilności z wersją Reacta używaną w projekcie.
Aby aktywować Adapter, musimy podpiąć go do Enzyme
w pierwszym pliku konfiguracyjnym 🙁 (i jedynym! :)) naszego środowiska testowego. W aplikacjach utworzonych za pomocą create-react-ap, Jest
spodziewa się, że powstanie on pod ścieżką /src/setupTests.js.
Na dodatek w każdym z testów chcemy mieć swobodny dostęp do trzech wariantów renderowania Enzyme
. Zaoszczędzimy sobie importowania i podepniemy je do obiektu globalnego.
Tyle w temacie konfiguracji, jesteśmy gotowi!
Więcej o enzyme dowiesz się z dokumentacji.
Alternatywą dla Enzyme
jest react-testing-library autorstwa Kenta C. Doddsa.
Aktualizacja z przyszłości: Swoje przygody z Jest i Enzyme opisałem w kolejnych wpisach z tej serii: „Jak pisać testy jednostkowe komponentów React z Jest i Enzyme” oraz „Jak pisać testy integracyjne komponentów React z Jest i Enzyme”.
Cypress.io
Listę narzędzi zamyka Cypress.io, framework do testów end-to-end. Ostatnimi czasy coraz więcej o nim słychać, praktycznie w samych superlatywach, więc rzucimy okiem na to, jak sprawdza się w praktyce.
I bez niespodzianek, więcej na temat cypress.io możesz przeczytać w dokumentacji ;).
Aktualizacja z przyszłości: o tym czy Cypress zasługuje na swoją reputację przeczytasz w artykule: „Jak pisać testy end-to-end aplikacji React z frameworkiem Cypress”.
Co będziemy testowali?
Tak się składa, że mamy do dyspozycji małą aplikację, która świetnie nadaje się do poddania testom. Jest nią crypto-tracker, który zbudowałem w ramach serii „Pierwszy projekt w ReactJS”. W trzech kolejnych wpisach zaprezentuję na jej przykładzie testy jednostkowe, integracyjne oraz end-to-end.