Nowoczesne testowanie kodu JS. Część Czwarta – testowanie asynchronicznego kodu javascript.

W ostatnich wpisach o testowaniu pokazałem z jakich narzędzi korzystam i jak używać ich do tworzenia prostych testów jednostkowych oraz do sprawdzania pokrycia kodu testami. Tym razem przedstawię testowanie asynchronicznego kodu javascript.

Asynchroniczny kod potrafi być zmorą wielu początkujących, i nie tylko, web developerów. Na szczęście, testowanie go nie jest takie trudne. Wystarczy wiedzieć kiedy ma się do czynienia z asynchronicznością i jakie narzędzie wykorzystać do testowania. Wiedza ta jest kluczowa, ponieważ źle napisane testy dla kodu asynchronicznego mogą mieć fatalne konsekwencje.

testowanie asynchronicznego kodu javascript

Nie będę rozwodził się nad tym czym jest asynchroniczność w JS, skoro jesteś na moim blogu to znaczy, że interesujesz się JavaScriptem i raczej wiesz o co chodzi. Jeżeli nie, polecam poszukać informacji na ten temat. Asynchroniczność to bardzo ważny aspekt programowania w JS i warto wiedzieć z czym to się je.

W skrócie, są to operacje nie blokujące przebiegu programu, czyli takie, które „nie czekają” na wynik danego wywołania. Najczęściej są to zapytania wysyłane do serwera lub bazy danych, ale nie tylko. W moim przykładzie wykorzystam metodę node’ową readFile, służącą do odczytywania zawartości pliku. Jest to operacja asynchroniczna, więc wywołanie readFile nie zatrzyma programu. Zaraz pokażę jaki wpływ będzie to miało na testy.

Testowany kod:

Poświęcę chwilę na objaśnienie tego co tu się dzieje. Stworzyłem klasę LineCounter, która ma służyć do liczenia linii w plikach tekstowych. Zawiera ona jedną metodę lineCount, która jako jako parametry przyjmuje nazwę pliku, oraz funkcję callbackową.

We wnętrzu lineCount wywołuję asynchroniczną metodę readFile należącą do node’owego obiektu fs. Do readFile przekazuję nazwę pliku z parametru lineCount oraz funkcję anonimową, którą dla czytelności przypisuję wcześniej do zmiennej processFile.

processFile wywoła się kiedy nie blokujące zapytanie readFile się zakończy. Jeżeli napotka błąd, zgłosi to w konsoli a w innym wypadku odpali callback z lineCount przekazując jako parametr liczbę linii w pliku nazwanym pliku tekstowym.

Skąd processFile będzie wiedziało o błędzie i liczbie linii? Funkcje asynchroniczne otrzymują jako dwa pierwsze parametry obiekt błędu err i data będące odpowiedzią na pierwotne zapytanie.

Generalnie nie jest to najpiękniejszy kod, ale w JS często widzi się podobne rzeczy, więc trzeba się przyzwyczajać 😛 Do tego, bardzo dobrze będzie się to testować.

Pierwsza próba, nieudana

Dobra, to zabieram się za pisanie testu. Nie skupiam się tym razem na idealnym pokryciu, chodzi jedynie o przetestowanie mechanizmu asynchroniczności. Tak wygląda zawartość mojego test.spec.js:

W before tworzę instancję LineCounter i w pierwszym it wywołuję jego metodę lineCount, jako pierwszy parametr przekazuję plik tekstowy który ma 3 linijki, a jako drugi metodę (która sama, przypominam, otrzyma ilość linii czytanego pliku jako parametr) zawierającą expect‚a porównującego otrzymaną z lineCount wartość z trójką. Wszystko wygląda sensownie.

Odpalam test i oto wynik:

wynikTestu pozytywny

Fajnie, ale coś mi nie gra. Zmienię treść testu na taką:

Puszczam test i co widzę:

wynikTestu pozytywny

Zdecydowanie coś tu nie gra 🙁

Prawidłowe podejście

W automatyzacji testów nie ma nic gorszego niż testy, które nie pokazują prawdy. Tego typu błędy mogą być naprawdę brzemienne w skutkach. Zaczynając na straconym czasie i zasobach a na braku zaufania w zespole i na linii klient – zespół kończąc 🙁 🙁 🙁 . Jednym słowem, trzeba bardzo uważać aby nie popełnić tego typu błędów.

No ale co się dzieje, dlaczego test kłamie? W zasadzie to wcale nie kłamie, po prostu robi coś innego niż może się wydawać. Problemem jest to, że Mocha uruchamiając scenariusz, nie zawierający żadnych asercji z automatu go zalicza. W sumie logicznie byłoby na odwrót, ale jest jak jest i w sumie nie wiem dlaczego.

Jednak mój scenariusz zawiera asercje, więc o co chodzi? Asercja znajduje się w callbacku, czyli w metodzie wykonanej dopiero gdy readFile otrzyma odpowiedź. readFile jednak działa asynchronicznie i zanim ta odpowiedź „wróci”, test już się zakończy. Z punktu widzenia Mocha, żadnej asercji nie ma.

No to jak zmusić test żeby „poczekał”. Można by było kombinować z jakimiś sleepami albo timeoutami, ale to nigdy nie jest dobra droga. Na szczęście nie ma potrzeby kombinować. Mocha ma w sobie odpowiednie mechanizmy, pozwalające na ogarnięcie takiej sytuacji. Oto jak od początku powinien wyglądać blok it:

Do funkcji anonimowej przekazuję nowy parametr done. Jest to metoda, dzięki której, test będzie „czekał”. Bez zagłębiania się w szczegóły, program, nie będzie uruchamiał się dalej, dopóki nie wywołana zostanie metoda done.

Wrzucam done po expect i oczekuję, że plik tesktowy będzie zawierał minus trzynaście linijek. Oto wynik:

wynikTestuZuy

Uff, wszystko działa jak należy 🙂

Podsumowanie

W Mocha mamy proste narzędzie do kontrolowania kodu asynchronicznego czyli done. Jednak, tak jak pisałem na początku, sztuką jest raczej wpadnięcie na to, kiedy z niego korzystać i jak. Mam nadzieję, że ten wpis pomoże to trochę ogarnąć, ale tak naprawdę najważniejsza jest praktyka. Im więcej testów się pisze, tym łatwiej to potem leci 🙂

Na dziś to tyle, w następnej części serii pokażę jak w testach radzić sobie z asynchronicznością 🙂 . Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem 🙂 .

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *