Pester – dlaczego?

Pester_1Kiedy dwa lata temu pierwszy raz zetknąłem się z Pesterem, przyznam bez bicia – nie specjalnie miałem pomysł jak to "ugryźć". Niby zdawałem sobie sprawę, jakie moduł ten ma zastosowanie i wiedziałem, że kod nie przetestowany to kod potencjalnie niebezpieczny… Niby przyjąłem do wiadomości, że pisząc skrypt w PowerShellu, który wykorzystywany będzie w środowisku produkcyjnym, de facto zbliżam się niebezpiecznie blisko do pozycji programisty… Z drugiej strony wydawało mi się dziwne, że w języku, który od zawsze błyszczał obiektowością, mam nagle testując sprowadzać wszystko do porównywania cyferek, ciągów znaków – generalnie, prostych właściwości. Może problem leżał w przykładach? Może ich prostota miast zachęcić, zniechęcała mnie swą (w moich oczach) kompletnie nieprzystającą do rzeczywistych problemów trywialnością? Przecież testowanie czy dwa plus dwa daje w rezultacie cztery nie da się za bardzo przełożyć na sprawdzenie, czy moja funkcja faktycznie usunie właściwego użytkownika w Active Directory…

Dziś, po przeszło roku intensywnego pisania testów mogę z czystym sumieniem powiedzieć: nie wyobrażam sobie pisanie kodu bez dodania testów. Sporadycznie zdarza się (z pośpiechu, braku czasu na przemyślenie metody stworzenia skutecznego testu) testy pominąć, ale to raczej wyjątki od reguły. Dziś postaram się Wam przybliżyć dlaczego testuję i dlaczego właśnie Pestera do testów wykorzystuję.

Nasza świnka morska

Zanim przystąpimy do testów i przyjrzymy się temu, jak testy te uczynić przydatnymi i dlaczego w ogóle moglibyśmy zechcieć testować, określmy funkcję, którą postaramy się przetestować i którą spróbujemy "poprawić": najpierw usuwając drobną usterkę, następnie dodając do niej nową funkcjonalność. Nasza funkcja łączy się z końcówką REST GitHuba i pobiera informacje o Pull Requestach istniejących w wybranym przez nas repozytorium. Informacje te pobierać będziemy bez uwierzytelniania (czyli pobrać będziemy mogli jedynie publiczne dostępne informacje).

function Get-GitHubPullRequest {
<#
.Synopsis
Function to read pull requests from GitHub
.Description
Function that uses REST endpoint to read information about Pull Requests available on GitHub.
It is required to provide it with:
- owner
- repository id
#>
[CmdletBinding()]
[OutputType('GitHub.PullRequest')]
param (
# Name of the owner of the repo (user or organization)
[Parameter(
Mandatory
)]
[String]$Owner,
# Name of the repository
[Parameter(
Mandatory
)]
[String]$Repository
)
$uri = "https://api.github.com/repos/$Owner/$Repository/pulls"
try {
$ProgressPreference = 'SilentlyContinue'
$pullRequests = Invoke-WebRequest -UseBasicParsing -Uri $uri -ErrorAction Stop
($pullRequests.Content | ConvertFrom-Json) | ForEach-Object {
$closedAt = try { [datetime]$_.closed_at } catch { '' }
[PSCustomObject]@{
PSTypeName = 'GitHub.PullRequest'
Title = $_.title
Body = $_.body
User = $_.user.login
CreatedAt = [dateTime]$_.created_at
ClosedAt = $closedAt
State = $_.state
}
}
} catch {
throw "Failed to read PRs from $Repository owned by $Owner - $_"
}
}

Wiemy już co będziemy testować, zanim przejdziemy do tego jak – zadajmy sobie pytanie nieco bardziej abstrakcyjne – po co?

Po co to testować?

Jaki jest cel testów? Osobiście testuję (a raczej testujemy – sprawa obejmuje całą grupę ludzi w mojej firmie, która odpowiedzialna jest za tworzenie kodu w PowerShellu) z kilku powodów.

Po pierwsze: kod posiadający odpowiednie testy łatwiej można zmienić. Dotyczy to zarówno zmian "kosmetycznych" (gdy staramy się kod uporządkować, uprościć, wzbogacić o obsługę błędów) jak i zmian bardziej wymiernych: usuwanie usterek bądź dodawanie nowych możliwości. Testy dają nam gwarancję, że zmieniając kod nie sprawiliśmy, że funkcja przestała działać.

Po drugie: dodając testy przed dodaniem funkcjonalności czy usunięciem usterki możemy łatwo potwierdzić, że nasz test jest poprawny (jeśli przed wprowadzeniem zmian test ma wynik pozytywny – to świadczy to o jego wadliwości) a także udowadniamy, że poprawki przyniosły oczekiwany rezultat. Oprócz tego, szczególnie w przypadku usterek, zagwarantować możemy, że usterka raz usunięta, pozostaje usunięta.

Po trzecie: testując możemy sprawdzić zachowanie naszego polecenia również w sytuacjach, które trudno uzyskać w rzeczywistości. Maskując poszczególne polecenia możemy zadbać o to, by prześledzić wszystkie ścieżki, którymi wędrować może logika w naszej funkcji/ skrypcie. Dzięki temu zyskujemy pewność, że nasz kod zachowa się tak, jak się spodziewamy. W zasadzie na tym etapie często zdarza się wyłapać "drobne" (a w konsekwencji – często katastrofalne w skutkach) usterki: literówki, błędy w logice, operacja przypisywania w warunkach logicznych…

Po czwarte: testując dajemy potencjalnym użytkownikom naszego kodu pewność, że obietnice złożone przez nas w pomocy do naszych poleceń są spełnione. Jakość i ilość testów, a także "pokrycie" kodu testami wpływa znacząco na zaufanie, jakim nasz kod gotowi są obdarzyć inni.

Po piąte: testy są koniecznością, gdy chcemy automatyzować. Publikowanie zaktualizowanego kodu bez automatycznych testów przypomina nieco rosyjską ruletkę. Im więcej kodu – tym więcej pocisków w naszym rewolwerze. Pokrycie kodu testami daje nam pewność, że publikujemy kod, który po prostu "działa".

Przyczyn jest pewnie więcej – wymieniłem jedynie te, którymi sam się kieruję. Ale dlaczego właśnie Pester?

Czym to to testować?

Prawdę powiedziawszy o Pesterze usłyszałem wtedy, gdy stał się on de facto standardowym językiem, w jakim pisano testy dla PowerShella (i w PowerShellu). I choć moduł ten w przeszłości miał "konkurencję" – wraz z dodaniem go do systemu kwestia została w zasadzie rozstrzygnięta. Ale choć na znalezienie odpowiedniego rozwiązania nie poświęciłem zbyt wiele czasu, podążając za resztą stada, to wypada wspomnieć dlaczego, moim zdaniem, to właśnie Pester zyskał tak wielką popularność (jeszcze zanim stał się częścią systemu operacyjnego).

Przede wszystkim Pester razi swą prostotą. Testy, których składnia ma wiele wspólnego z przedstawianiem za pomocą kodu intencji testującego, łatwo jest zrozumieć i przeanalizować. Przeciętny test wygląda jak zdanie pisane w języku angielskim. Oczywiście, pewne fragmenty kodu pod tym względem nie będą tak czytelne, ale sama intencja twórcy testów jest zwykle łatwa do określenia.

Kolejna przyczyna to łatwość "maskowania" poleceń: sposób, w jaki zaimplementowano to w Pesterze umożliwia niemal dokładne odwzorowanie zachowania naszej funkcji. Funkcja emulująca "z automatu" mieć będzie te same parametry, łącznie z typem i innymi, istotnymi z punktu widzenia testów właściwościami. Funkcje naśladujące rzeczywistość są de facto funkcjami zastępczymi, stąd ta dokładność w odwzorowywaniu zachowania pierwowzoru.

Rzecz o kolosalnym znaczeniu to świetna dokumentacja. Obecnie można kupić o Pesterze książkę – ale szkoda by mi było pieniędzy na nią, skoro autorzy modułu oferują olbrzymią ilość wiedzy za darmo.  W dodatku chętnie dzielą się wiedzą w dokumentacji nie uwzględnioną. Dokumentacja ta pomaga nie tylko opanować sam moduł, zawarte w nim polecenia czy składnię, ale również pomaga "wystartować" z testami.

Dodatkowo moduł wspiera różne formaty wyjściowe. Co ciekawe – możemy wyjście "konsumowalne" dla maszyn (dla przykładu XML w formacie zgodnym z pakietem NUnit) łączyć z takim, które przyda się użytkownikom. Moduł z założenia zwraca bardzo czytelną (choć może w ocenie niektórych – nazbyt barwną) informację o tym, co przetestowano, w jakim kontekście testy przeprowadzono, oraz jaki był ich rezultat. Jeśli zależy nam by na wyjściu uzyskać obiekt – mamy również taką możliwość.

To wszystko rozmaite przyczyny nie uwzględniające przyczyny najważniejszej: moduł ten to w chwili obecnej "standardowy" mechanizm testowania kodu pisanego w PowerShellu. Stał się on wręcz synonimem przeprowadzania testów. "Pasteryzując" kod nie musimy jednocześnie martwić się o dostępność samego narzędzia. W najnowszych wersjach systemu jego obecność jest równie pewna, jak obecność samego PowerShella. Jest on również częścią składową PowerShella dostępnego dla różnych platform. Możemy więc już dziś uruchamiać testy na Linuksie.

Co dalej?

Tyle na dziś… Wiemy już dlaczego, w kolejnych częściach przyjrzymy się temu jak pokryć naszą funkcję testami a następnie – jak może wyglądać wprowadzanie zmian, gdy testy poprzedzają napisanie pierwszej linii "właściwego" kodu.

~ - autor: Bartek Bielawski w dniu 15 marca, 2017.

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: