Moduł ISEPester – testujemy w PowerShell ISE
O testach w PowerShellu pisałem już na tym blogu kilka razy. Dziś skupię się nie na samych testach, ich tworzeniu i zastosowaniach, ale na module, który “popełniłem” inspirowany sesją przygotowaną niemal dwa lata temu przez Jakuba Jareša, człowieka w znacznej mierze odpowiedzialnego za moduł Pester. W sesji tej Jakub demonstrował jak w VS Code uruchamiać pojedynczy test, czy blok Context/ Describe. Po ustaleniu, że wtyczka do VS Code korzysta po prostu z odpowiedniego rodzaju filtrów w trakcie uruchamiania testów, nie pozostało mi nic innego, jak tylko przenieść te możliwości do mojego ulubionego edytora, PowerShell ISE.
Zacznijmy od tego, że rozwiązanie to dostępne jest jedynie w wersji piątej Pestera. Jeśli z jakiegoś powodu zmuszeni jesteś korzystać z wersji czwartej – to niestety, metoda opisywana na nic się Wam nie przyda. Sam moduł nazywa się ISEPester i znajdziecie go w galerii PowerShella i na GitHubie.
Jeśli jednak korzystać możecie z wersji piątej, to pewnie wiecie, że Pester od tej wersji korzysta ze specjalnie spreparowanego obiektu konfiguracji. Owszem, w najprostszych przypadkach możemy nadal test uruchomić bez niego, przykładowo uruchomimy plik z testem wykorzystując jedną z dwu składni uzyskując dokładnie ten sam efekt:
Invoke-Pester -Path .\Connect-EWSService.tests.ps1 -Output Detailed | |
$konfiguracja = [PesterConfiguration]@{ | |
Run = @{ | |
Path = '.\Connect-EWSService.tests.ps1' | |
} | |
Output = @{ | |
Verbosity = 'Detailed' | |
} | |
} | |
Invoke-Pester -Configuration $konfiguracja |
Uproszczona składnia jednak to rozwiązanie znacznie ograniczające nasze możliwości. Tak jest też i w tym przypadku – bez obiektu konfiguracji ani rusz.
Jak taki obiekt utworzyć? Najlepsza metoda to skorzystać z właściwości statycznej. Dalej możemy zmieniać właściwości tak utworzonego obiektu. Co ciekawe, każda z właściwości posiada informację zarówno o wartości bieżącej (Value), domyślnej (Default), jak i pełny opis (wraz z dozwolonymi wartościami, jeśli dany parametr akceptuje tylko jedną ze zdefiniowanych wartości:
$konfiguracja = [PesterConfiguration]::Default | |
$konfiguracja.Output.Verbosity = 'Detailed' | |
$konfiguracja.Output.Verbosity | |
<# | |
Na wyjściu... | |
Default Description Value | |
------- ----------- ----- | |
Normal The verbosity of output, options are None, Normal, Detailed and Diagnostic. Detailed | |
#> |
Nas interesować będzie szczególnie jedno ustawienie, którego nie da się modyfikować wykorzystując składnię uproszczoną: filtrowanie. Otóż efekt uruchomienia pojedynczego bloku It/Context/Describe wymaga zdefiniowania filtru obejmującego konkretną linię kodu, w konkretnym pliku. Ważne, by linia ta zawierała właśnie jedno z trzech wymienionych poleceń.
Zanim jednak dobierzemy się do filtrów, musimy ustalić plik testu, który zamierzamy uruchomić. W PowerShell ISE to bajecznie proste: wbudowany obiekt $psISE posiada właściwość CurrentFile. Ta zaś zawiera informację o stanie edytora (przyda nam się to nieco później) oraz o ścieżce do aktualnie otwartego pliku:
Jeśli mamy więc otwarty plik, to sprawdzić musimy jeszcze:
- czy plik ten istnieje na dysku (inaczej nie możemy go uruchomić w ten sposób)
- czy w edytorze wybrano jakąkolwiek linię
- czy jesteśmy w stanie określić linię, którą wybrano
Jeśli powyższe warunki są spełnione to wiemy już, jaki test musimy uruchomić. Dodatkowo możemy uprzedzić użytkownika, jeśli próbuje uruchomić test w pliku ze zmianami, które nie zostały jeszcze zapisane:
if ( | |
($file = $psISE.CurrentFile) -and | |
(Test-Path -LiteralPath $file.FullPath) -and | |
($line = $file.Editor.CaretLineText) -and | |
($lineNumber = $file.Editor.CaretLine) | |
) { | |
if (-not $file.IsSaved) { | |
Write-Warning -Message "File $($file.FullPath) is not saved - working on current copy on disk!" | |
} | |
$config = [PesterConfiguration]@{ | |
Run = @{ | |
Path = $file.FullPath | |
} | |
} | |
# ... | |
} |
Wiemy, że filtr musi zawierać linię z odpowiednim poleceniem. Możliwości są trzy: kursor jest na takiej właśnie linii, kursor umieszczono wewnątrz testu (dowolne miejsce w bloku It), lub wersja samobójcza, gdy kursor nie znajduje się wewnątrz testu (lub po prostu nie znajduje się w pliku z testami).
Zacznijmy od opcji najprostszej: w takim przypadku mamy już komplet informacji, możemy więc od razu zdefiniować filtr:
if ($line -match '\s*(Describe|Context|It)') { | |
$config.Filter.Line = '{0}:{1}' -f $file.FullPath, $lineNumber | |
} |
W drugim przypadku z pomocą przyjdzie nam AST: odszukamy blok It, w którym znajduje się nasz kursor i to linię w której blok się ten znajduje przekażemy do filtra:
$parsedTestFile = [System.Management.Automation.Language.Parser]::ParseFile($file.FullPath, [ref]$null, [ref]$null) | |
$myItBlock = $parsedTestFile.FindAll( | |
{ | |
param ( | |
$Ast | |
) | |
$Ast.CommandElements -and | |
$Ast.CommandElements[0].Value -eq 'It' -and | |
$Ast.Extent.StartLineNumber -le $lineNumber -and | |
$Ast.Extent.EndLineNumber -ge $lineNumber | |
}, | |
$true | |
) | |
if ($myItBlock) { | |
$config.Filter.Line = '{0}:{1}' -f $file.FullPath, $myItBlock[0].Extent.StartLineNumber | |
} |
W ostatnim przypadku nie jesteśmy w stanie ustalić, co autor miał na myśli – dajemy więc za wygraną.
Oprócz uruchamiania testów, moduł umożliwi nam też zmianę sposobu wyświetlania wyników – dla przykładu, możemy zdecydować, by Pester wyświetlił dane diagnostyczne:
Set-ISEPesterConfiguration -Verbosity Diagnostic |
Na koniec – krótki filmik o korzystaniu z ISEPester. Smacznego!