Pester – jak zacząć?
W pierwszej części cyklu o testowaniu w PowerShellu skupiłem się na tym dlaczego testuję i dlaczego właśnie Pestera w testowaniu wykorzystuję. W tej części przyjrzymy się temu, jak zadbać o to, by móc z Pestera skorzystać oraz przyjrzymy się składni testów, powoli pokrywając naszą świnkę morską testami. Mam nadzieję, że w pierwszej części przekonałem Was, że warto testować. I jeśli nawet nie przekonałem, że Pester jest pod tym względem najlepszym wyborem, to myślę, że warto kontynuować lekturę cyklu: nawet jeśli korzystać będziecie w testach z innych narzędzi, to pomijając może samą instalację modułu, artykuł może ułatwić rozpoczęcie tej podróży. Od razu zaznaczyć muszę jednak, że nie robiłem jakiegoś potężnego "riserczu", treść wynika z osobistych doświadczeń jedynie.
Jak to to zainstalować?
Zacznijmy od przypadku najprostszego. A raczej kilku takich przypadków. Przede wszystkim: Windows 10 i Windows Server 2016. W obu przypadkach instalacją Pestera nie musimy się przejmować, oba systemy zostały w niego wyposażone "z pudełka". Troszkę trudności może być w przypadku aktualizacji: wersja wbudowana podpisana jest przez Microsoft, wersja w galerii PowerShella niekoniecznie… Nie zmienia to faktu, że Pester po prostu już tam na nas czeka.
Drugi przypadek szczególny to PowerShell na inne platformy. Pester również został w nim zawarty, więc możemy zacząć z niego korzystać bez konieczności instalowania go w ten czy inny sposób. Samo zainstalowanie PowerShella na tych platformach tożsame jest z zainstalowaniem Pestera.
Sprawa jest również dość prosta w przypadku maszyn z zainstalowanym WMF w wersji 5. Sam Pester nie jest elementem tego pakietu, ale zyskujemy po jego zainstalowaniu dostęp do galerii PowerShella. Zainstalowanie Pestera wymaga więc jedynie wydania polecenia Install-Module z odpowiednimi parametrami.
Moduły niezbędne do korzystania z PowerShell Gallery możemy też zainstalować w przypadku posiadania starszej wersji WMF. Link do pakietu instalacyjnego znajdziemy na stronie samej galerii. Wystarczy skorzystać z pobranego z tej strony pakietu MSI. Po zainstalowaniu galerii ponownie możemy doinstalować Pestera korzystając z polecenia Install-Module.
Ostatnia opcja to pobranie modułu bezpośrednio z GitHuba, czy to przez sklonowanie repozytorium, czy też przez pobranie jednej z udostępnianych tam wersji. Jak więc widać opcji jest sporo.
Jak to to używać?
Zanim zaczniemy pisać nasze testy, przyjrzyjmy się po krótce tym elementom składni, z których z pewnością (prędzej czy później) skorzystamy. Warto wspomnieć, że Pester tworzy swoisty DSL (Domain Specific Language). Poszczególne jego elementy stanowią de facto polecenia. Elementy potomne umieszczamy w bloku skryptu rodzica. Blok ten zaś stanowi argument polecenia, które określa jak rodzic powinien się zachować. Warto o tym pamiętać, szczególnie jeśli preferujemy umieszczać nawiasy klamrowe na początku kolejnej linii kodu. Co ciekawe, Pester rozpoznaje ten dość częsty błąd i gdy tak postąpimy wyświetli informację, która z powodzeniem może pretendować do tytułu najbardziej pomocnego błędu wszechczasów:
No test script block is provided. (Have you put the open curly brace on the next line?)
Zacznijmy od Describe, który jest elementem, od którego zwykle zaczynać będziemy pisanie naszych testów. Element ten stanowi swoisty pojemnik, do którego wrzucać będziemy testy, definicje poleceń maskujących rzeczywiste polecenia oraz opcjonalne elementy typu Context (pozwalające tworzyć podgrupy w ramach jednego bloku Describe). Obu pojemnikom nadajemy nazwę, możemy otagować a wszelkie elementy potomne umieścimy w bloku skryptu. Dzięki tagom możemy pomijać pewne grupy testów. Pozostałe parametry są wymagane: nazwa bloku oraz blok skryptu zawierający elementy potomne. Przykładowy, pusty blok Describe:
Describe 'Testujemy, co się stanie gdy polecenia wybuchną nam w rękach' { | |
} |
Kiedy skorzystamy z bloku Context? Jego główne zastosowanie to ograniczenie zasięgu pewnych elementów składni do kilku testów i jednoczesne współdzielenie (w ramach bloku describe) innych elementów. W przypadku naszej świnki morskiej (przynajmniej na tym etapie) ograniczymy się do bloków Describe.
Właściwe testy umieszczamy w blokach It, które zawierać będą elementy, pozwalające ustalić czy wynik rzeczywisty pokrywa się z wynikiem oczekiwanym. Praktyce blok It wykonuje polecenie (lub kilka poleceń) i albo nie zwraca nic (wówczas wynik testu będzie pozytywny), lub zwraca wyjątek (negatywny wynik testu). Najłatwiej tego właśnie reakcję uzyskać korzystając z polecenia Should, które zwróci odpowiedni wyjątek w oparciu o wynik porównania. Istnieje wiele takich operatorów, które zawsze możemy odwrócić korzystając z przełącznika Not.
Ostatni element, niezbędny by rozpocząć pisanie testów to polecenie Mock, dzięki któremu maskować będziemy prawdziwe polecenia. Dzięki temu będzie mogli przetestować działanie naszego kodu niejako w oderwaniu od kodu, który nie jest aktualnie przez nas testowany. Chcemy sprawdzić, czy nasz kod robi to, co powinien. Musimy więc najpierw zadbać o to, by uruchamiane polecenia zachowały się tak, jak tego oczekujemy. W naszym przypadku: możemy zagwarantować poprawne działanie naszych testów nawet wtedy, gdy nie będziemy mieli dostępu do samego GitHuba. Nie musimy tworzyć repozytorium, Pull Requestów i wszystkich innych elementów, które będziemy testować. Wystarczy, że polecenie maskujące zwróci obiekty łudząco przypominające te, które zwróciłoby polecenie właściwe. Zastąpimy prawdziwe polecenie, łączące się z API (Invoke-WebRequest) odpowiednim poleceniem maskującym. Ważna informacja: polecenie Mock działać będzie jedynie dla poleceń istniejących. O tym, jak radzić sobie w przypadku, gdy maskowane polecenie nie istnieje, pomówimy w jednej z kolejnych części. W ogóle samemu zagadnieniu poświęcimy przynajmniej jeden, odrębny artykuł: symulowanie bywa kłopotliwe a jakość naszych testów w duże mierze zależy od tego jak wiernie oddamy rzeczywistość i na ile trafnie określimy różne scenariusze, które mogą nam się przydarzyć w trakcie korzystania z zastępowanego polecenia.
Wypełnijmy więc nasz blok Describe kilkoma testami. Chcemy przetestować zachowanie całej funkcji w sytuacji, gdy Invoke-WebRequest zwróci błąd – zamaskujemy więc to polecenie wersją niezwykle powtarzalną, wybuchającą za każdym razem. Następnie sprawdzimy, czy błąd zwrócony jest taki, jakim chcielibyśmy go widzieć:
Describe 'Testujemy, co się stanie gdy polecenia wybuchną nam w rękach' { | |
Mock -CommandName Invoke-WebRequest -MockWith { throw 'Bum!' } | |
try { | |
Get-GitHubPullRequest -Owner Test -Repository TestRepo | |
} catch { | |
$exception = $_.Exception | |
} | |
It 'Jak Invoke-WebRequest wybuchnie, dostaniemy błąd z właścicielem' { | |
$exception.Message | Should Match 'Owned by Test' | |
} | |
It 'Jak Invoke-WebRequest wybuchnie, dostaniemy błąd z repozytorium' { | |
$exception.Message | Should BeLike '* from TestRepo *' | |
} | |
It 'Jak Invoke-WebRequest wybuchnie, nasz błąd też będzie wybuchowy' { | |
$exception.Message | Should BeLike '* Bum!' | |
} | |
} |
Jeśli nasze testy zapiszemy w pliku, którego nazwa kończy się na Tests.ps1, to by uruchomić testy wystarczy nam wykonać polecenie Invoke-Pester. Polecenie to ma kilka dodatkowych parametrów, którym więcej uwagi poświęcimy w kolejnych częściach cyklu. Na tym etapie uruchomimy je bez żadnego parametru:
Nasze testy się powiodły, ale warto już na tym etapie zadać sobie trud zweryfikowania, czy nas test może zwrócić wynik negatywny. Możliwości są dwie: zmieniamy oczekiwania (w porównaniach popełniając jakąś literówkę, lub wręcz porównanie odwracając), bądź dane wejściowe. Zmiana jednego z tych czynników powinna skutkować negatywnym rezultatem. Nie ma doprawdy gorszego testu niż taki, który nigdy nie zwraca wyniku negatywnego. Daje nam on bowiem pozorne poczucie bezpieczeństwa.
Na tym kończymy drugą część – w kolejne przyjrzymy się temu, jak tworzyć kolejne testy i jak ocenić na ile naszą funkcję (skrypt, moduł) przetestowaliśmy. Z konieczności postaramy się też o nieco bliższe rzeczywistości maskowanie polecenia Invoke-WebRequest.