Tworzymy moduł: testy jednostkowe

neonowkaKiedy kilka lat temu usłyszałem po raz pierwszy o testach jednostkowych to uznałem, że to chyba nadmiar czasu po stronie moich kolegów powoduje, że zamiast przeznaczyć czas na pisanie kodu, który coś robi, piszą kod, który udaje jedynie, że coś robi. W pewnym jednak momencie postanowiłem spróbować i pomysłem tym zaraziłem swoich kolegów. A może to oni zarazili mnie…? Tak czy inaczej, zaczęliśmy pisać testy do naszych nowotworzonych funkcji i bardzo szybko okazało się, że testy robią i to bardzo dużo.

Dziś już nie wyobrażam sobie tworzenia funkcji bez testów (prawdę powiedziawszy – reszta zespołu zwyczajnie by mi na taki „luksus” nie pozwoliła). I nie zliczę okazji, gdy nawet elementarne testy nie pomogły mi uniknąć pełnego błędów kodu pracującego w środowisku produkcyjnym. Dziś przyjrzymy się temu, jak tworzyć testy przy pomocy Pestera w wersji piątej. Wersja modułu jest ważna, gdyż starsze wersje miały nieco inne wymogi dotyczące struktury testu, nieco inną składnię i nieco inne możliwości.

Zanim jednak przyjrzymy się składni słów kilka o samej koncepcji testowania kodu – w kontekście PowerShella, naturalnie.

Większość kodu, który piszemy w PowerShellu będzie koncentrowała się wokół uruchamiania rozmaitych poleceń. Pojawią się też z pewnością operacje na obiektach: będziemy sprawdzać właściwości, uruchamiać metody. Testy jednostkowe posłużą nam do tego, by wszelkie operacje tego typu „emulować” i upewnić się, że nasza „paczka” działa zgodnie z oczekiwaną przez nas logiką. Możemy więc sprawdzić, czy zostanie uruchomione odpowiednie polecenie, czy polecenie uruchomione zostanie z właściwym zestawem parametrów. Wreszcie możemy sprawdzić, czy wykonane zostaną zdefiniowane wewnątrz obiektów metody i czy przekazane zostaną do nich odpowiednie parametry.

Cel testów jednostkowych to po pierwsze: sprawdzenie, czy założenie pokrywa się z rzeczywistością. A po drugie: umożliwienie wprowadzania zmian (dodawania funkcjonalności, usuwanie usterek, poprawianie czytelności kodu) bez ryzyka, że coś co wcześniej działało, nagle działać przestanie. Testy stanowią też swoistą dokumentację: to, co czasem może nam umknąć w opisie i przykładach z pewnością stanie się jasne, gdy czytając testy podejrzymy, „co autor miał na myśli”.

Sam proces testowania składa się z kilku elementów. Po pierwsze: musimy zamaskować (mock) polecenia w taki sposób, by nasze testy przypadkowo nie opróżniły AD, nie usunęły połowy plików z naszego dysku a jednocześnie działały nawet wtedy, gdy nie mamy dostępu do zasobów niezbędnych do tego, by nasze polecenia działały.

Druga czynność to uruchomienie testowanej przez nas funkcji z naszego modułu z wybranym zestawem parametrów. Oczywiście, dobrze przetestować wszystkie kombinacje oferowane użytkownikowi. Dzięki temu będziemy mieli pewność, że działają one poprawnie.

Trzeci element to sprawdzenie, czy wszystko poszło zgodnie z planem – polecenia zostały uruchomione, odpowiedni wynik został zwrócony, obiekty zostały utworzone, a metody i właściwości wykorzystane wewnątrz naszej funkcji.

Testy umieszczamy w pliku o nazwie odpowiadającej naszej funkcji z dodanym „Tests.ps1” – dzięki temu Pester sam je znajdzie i uruchomi. Struktura samego testu wymaga umieszczenia testów wewnątrz bloku Describe. Blok ten może zawierać mniejsze bloki: Context. Indywidualne testy umieszczamy w blokach It. Resztę kodu: tworzenie masek poleceń, definiowanie zmiennych – umieścimy w blokach BeforeAll, AfterAll, BeforeEach i AfterEach. Każdy z tych bloków obejmować będzie dany blok i bloki potomne. Kolejność wykonywania:

  • blok BeforeAll w blokach nadrzędnych (jeśli istnieją)
  • blok BeforeAll w bloku bieżącym
  • sekwencja testowa składająca się z następujących bloków
    • blok BeforeEach w blokach nadrzędnych
    • blok BeforeEach w bloku bieżącym
    • blok It
    • blok AfterEach w bloku bieżącym
    • blok AfterEach w blokach nadrzędnych
  • blok AfterAll w bloku bieżącym
  • blok AfterAll w blokach nadrzędnych

Jak widać tworzy się swoista „kanapka”.

Tyle teorii – łatwiej będzie zrozumieć jak przejdziemy do praktyki.

Przygotujemy najprostszy zestaw testów dla polecenia z naszego przykładowego modułu. Sprawdzimy, czy obsługa błędów działa poprawnie. Przekonamy się, czy polecenie Invoke-RestMethod jest uruchamiane z odpowiednimi parametrami. Wreszcie sprawdzimy, czy właściwość dodawana przez nas faktycznie pojawia się na wyjściu. Testów jest niewiele, więc tworzenie osobnego kontekstu wydaje się przesadą, ograniczymy się więc do jednego bloku Describe zawierającego jedynie blok BeforeAll (w którym zdefiniujemy maskę polecenia Invoke-RestMethod) oraz wspomniane powyżej testy.

Describe 'Testujemy funkcję Get-PowerShellPLPost' {
BeforeAll {
Mock -CommandName Invoke-RestMethod -MockWith {
@(
@{
title = 'test'
link = 'http://link.do/test'
pubDate = '2020-12-28 10:00'
}
)
}
}
# Nasze testy w blokach It
}

Jak widać prawdziwe polecenie zastąpiłem blokiem kodu, który zwróci wynik zbliżony do tego, który uzyskalibyśmy normalnie. Dzięki temu, że prawdzie polecenie zostało zamaskowane mogę być pewien, że testy działać będą nawet wówczas, gdy uruchomione zostaną na systemie nie mającym dostępu do mojego bloga. Taka izolacja pozwala nam skupić się na testowaniu naszego kodu, a nie poprawności działania kodu napisanego przez innych.

Na początek testujemy, czy obsługa błędów zadziała poprawnie. W tym celu najpierw zamaskujemy polecenie w taki sposób, by wygenerowało błąd który w normalnych warunkach nie przerwałby działania naszego kodu. Domyślnie nasza funkcja powinna zadbać, by do przerwania doszło, by poprawnie zadziałał blok try/catch.

It 'Zwraca błąd z info o problemach w Invoke-RestMethod' {
Mock -CommandName Invoke-RestMethod -MockWith {
Write-Error 'Błąd!'
}
{
Get-PowerShellPLPost -ErrorAction Stop
} | Should -Throw -ExpectedMessage 'Coś poszło nie tak... Błąd!'
}

Kolejny test sprawdzi, czy nasza funkcja odpowiednio tłumaczy przekazane parametry na sposób uruchomienia polecenia Invoke-RestMethod. Dzięki temu zyskamy pewność, że nasz parametr działa poprawnie i oczekiwania użytkownika zostaną spełnione.

It 'Uruchamia Invoke-RestMethod z odpowiednimi parametrami' {
$null = Get-PowerShellPLPost -Page 10
Should -Invoke Invoke-RestMethod -ParameterFilter {
$Uri -eq 'https://powershellpl.net/feed/?paged=10'
}
}

Tu uwaga: w poprzednim bloku It zamaskowaliśmy polecenie Invoke-RestMethod wersją, która zwraca błąd. W kolejnym bloku jednak ta maska już nie będzie wykorzystywana – Pester sam wróci do maski „domyślnej”, zdefiniowanej w bloku BeforeAll.

Wreszcie test ostatni – sprawdzający fragment kodu odpowiedzialny za domyślnie wyświetlane właściwości. Sprawdzimy w tym miejscu, czy na wyjściu naszego polecenia odpowiednia właściwość zostanie dodana do obiektu.

It 'Zwraca obiekt z zestawem właściwości DefaultDisplayPropertySet' {
$wynik = Get-PowerShellPLPost
$wynik.PSStandardMembers | Should -Not -BeNullOrEmpty
}

Testy uruchomimy korzystając z polecenia Invoke-Pester. Testy od razu pokazały swą przydatność. Okazało się, że nasza obsługa błędów nieco kuleje:

Pester-Failed-Test

Poprawiamy kod, importujemy zaktualizowany moduł i uruchamiamy testy podobnie. Tym razem jest już „zielono”:

Pester-Green-Test

Testy oczywiście bywają bardziej skomplikowane. Ważne by pamiętać w trakcie ich tworzenia by nie przesadzić w naszych poleceniach maskujących. Im ich wynik bliższy będzie rzeczywistemu, tym większa szansa, że testy wyłapią błędy w naszym kodzie. W skrajnych przypadkach można wręcz rozważyć eksport wyniku rzeczywistego do pliku CliXML – w takim wypadku będziemy mieli pewność, że maskując polecenia nie maskujemy błędu w logice naszego kodu. Dodatkowo jeśli wyłapiemy jakąś usterkę w naszym kodzie to warto od razu dodać odpowiedni test, który pozwoli nam uniknąć „podróży w czasie”, kiedy to naprawione usterki wracają jak bumerang lub niezapłacony weksel… Winking smile

~ - autor: Bartek Bielawski w dniu 30 grudnia, 2020.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: