Apprenez à écrire des tests contre PowerShell Universal avec Pester 5.
Installation des prérequis
Dans cet article, nous utiliserons InvokeBuild et Pester. Vous devrez installer ces modules pour suivre.
Install-Module Pester
Install-Module InvokeBuild
Télécharger PowerShell Universal
La première étape consiste à télécharger et installer la version de PowerShell Universal contre laquelle vous voulez tester votre configuration. Vous pouvez le faire de deux façons. Vous pouvez utiliser Invoke-WebRequest directement pour télécharger le ZIP, ou utiliser Install-PSUServer pour télécharger le MSI et installer le service.
Dans cet exemple, j’utiliserai Invoke-WebRequest puisque je télécharge la build nightly. Le script suivant lit le stockage blob nightly, trouve la dernière build, télécharge le ZIP, l’extrait et débloque les fichiers.
[xml]$Xml = (Invoke-RestMethod 'https://imsreleases.blob.core.windows.net/universal-nightly?restype=container&comp=list').Substring(3)
$MaxBlob = $null
foreach($blob in $Xml.EnumerationResults.Blobs.Blob.Where({$_.Url.Contains('win7') -and $_.Url.Contains(".zip")}))
{
if ($null -eq $MaxBlob -or ([int]$blob.Name.Split('/')[0]) -gt ([int]$MaxBlob.Name.Split('/')[0]))
{
$MaxBlob = $blob
}
}
Invoke-WebRequest $MaxBlob.Url -OutFile "$PSScriptRoot\Universal.zip"
Expand-Archive -Path "$PSScriptRoot\Universal.zip" -Destination "$PSScriptRoot\Universal"
Get-ChildItem "$PSScriptRoot\Universal" -Recurse | Unblock-File
Vous pourriez aussi adapter le script ci-dessus pour télécharger une version publiée.
Invoke-WebRequest "https://imsreleases.blob.core.windows.net/universal/production/2.5.4/Universal.win7-x64.2.5.4.zip" -OutFile "$PSScriptRoot\Universal.zip"
Configurer PowerShell Universal
Une fois PowerShell Universal téléchargé, nous pouvons configurer PSU avec des fichiers de configuration déjà préparés. Dans notre suite de tests d’intégration, nous avons des fichiers .ps1 que nous chargeons dans le répertoire de configuration avant de démarrer le serveur.
Nos fichiers de test couvrent de nombreuses permutations de configuration PSU. Voici un extrait de notre endpoints.ps1.
New-PSUEndpoint -Url "/get" -Method 'GET' -Endpoint {
"Hello"
}
New-PSUEndpoint -Url "/get/header" -Method 'GET' -Endpoint {
$Headers["X-MYHEADER"]
}
New-PSUEndpoint -Url "/get/:id" -Method 'GET' -Endpoint {
$Id
}
New-PSUEndpoint -Url "/post" -Method 'POST' -Endpoint {
$Body
}
New-PSUEndpoint -Url "/post/params" -Method 'POST' -Endpoint {
param($Name, $Value)
@{
Name = $Name
Value = $Value
}
}
New-PSUEndpoint -Url "/error" -Endpoint {
throw "Uh oh!"
} -ErrorAction stop
Nous prenons ensuite cette configuration et la déployons dans le répertoire C:\ProgramData\UniversalAutomation\Repository.
New-Item C:\ProgramData\UniversalAutomation -ItemType Directory
Copy-Item "$PSScriptRoot\assets\Repository" C:\ProgramData\UniversalAutomation -Recurse
Préparer un test Pester
Ensuite, nous pouvons démarrer le serveur PSU et le préparer pour notre suite de tests Pester. Le script suivant démarre le serveur PSU, attend qu’il soit actif, ouvre une session, puis accorde un app token que nous pourrons utiliser plus tard dans les tests.
Import-Module "$PSScriptRoot\Universal\Universal.psd1"
$Process = Start-Process "$PSScriptRoot\Universal\Universal.Server.exe" -PassThru
# Wait for the PSU server to start
while($true)
{
try
{
Invoke-RestMethod "http://localhost:5000/api/v1/alive"
break;
}
catch
{
}
}
try
{
# Sign in using default forms auth.
Invoke-RestMethod "http://localhost:5000/api/v1/signin" -Method Post -Body (@{
Username = "admin"
Password = "admin"
} | ConvertTo-Json) -SessionVariable Session -ContentType "application/json"
# Grant an app token using the login session
$ENV:TestAppToken = (Invoke-RestMethod "http://localhost:5000/api/v1/apptoken/grant" -Method GET -WebSession $Session).Token
# Connect to the PSU server so we can use cmdlets
Connect-PSUServer -AppToken $ENV:TestAppToken -ComputerName "http://localhost:5000"
# Set the current location and start executing tests
Set-Location $PSScriptRoot
$Results = Invoke-Pester -PassThru
if ($Results.Result -ne 'Passed')
{
throw "Tests failed!"
}
}
finally
{
Stop-Process $Process
}
Écrire un test Pester
L’étape suivante consiste à exécuter les scripts de validation avec Pester. Cet article a été écrit avec Pester 5.3.1.
Voici un sous-ensemble des tests que nous exécutons pour les scripts. Nous tirons parti d’un test data-driven qui nous permet de cibler plusieurs environnements en fournissant un tableau -ForEach au bloc Describe.
Tous les tests de cet exemple s’exécuteront trois fois, une fois pour chaque environnement. Nous utilisons aussi le bloc BeforeAll pour définir l’environnement avant les tests de chaque environnement.
Enfin, chaque bloc It valide une fonctionnalité, avec des applets de commande comme Invoke-PSUScript.
Describe "Scripts.<_>" -ForEach @('pwsh', 'powershell', 'integrated') {
BeforeAll {
Set-PSUSetting -DefaultEnvironment $_
}
Context "Run" {
It "should have correct variables" {
$Vars = Invoke-PSUScript -Name 'Vars.ps1' -Wait
$Vars.Environment | Should -be $_
$Vars.Script.Name | Should -be "Vars.ps1"
$Vars.Job | Should -not -be $null
$Vars.JobId | Should -not -be $null
$Vars.ScriptId | should -not -be $null
$Vars.Simple | Should -be "123"
}
It "should run a script by name" {
$Job = Invoke-PSUScript -Name 'Script.ps1'
$Job | Should -not -be $null
}
It "should run a script by pipeline" {
{ Get-PSUScript -Name 'Script.ps1' | Invoke-PSUScript } | Should -Throw
}
It "should return a hashtable" {
$Output = Invoke-PSUScript -Name 'Output.ps1' -Wait
$Output.Name | Should -be 'Tutorial'
$Output.Description | Should -be 'Tutorial'
}
It "should pass parameters to script" {
$Output = Invoke-PSUScript -Name 'Params.ps1' -Wait -One 1 -Two 2
$Output.One | Should -be 1
$Output.Two | Should -be 2
}
It "should call script in folder" {
Invoke-PSUScript -Name 'Script2.ps1' -Wait | Should -be 'Hello'
}
}
}
Empaqueter dans un script InvokeBuild
Maintenant que le cadre de test et les tests sont en place, nous pouvons les encapsuler dans un script InvokeBuild. Cela facilite l’appel de chaque portion du flux.
Voici l’exemple complet. Nous avons défini trois tâches de build. L’une nettoie les exécutions de tests précédentes. La deuxième télécharge et extrait la build nightly. La dernière exécute la suite de tests.
task Clean {
Remove-Item -Path "C:\ProgramData\PowerShellUniversal" -Force -ErrorAction SilentlyContinue -Recurse
Remove-Item -Path "C:\ProgramData\UniversalAutomation" -Force -ErrorAction SilentlyContinue -Recurse
Remove-Item -Path "$PSScriptRoot\Universal" -Force -ErrorAction SilentlyContinue -Recurse
Remove-Item -Path "$PSScriptRoot\Universal.zip" -Force -ErrorAction SilentlyContinue
}
task DownloadNightly {
[xml]$Xml = (Invoke-RestMethod 'https://imsreleases.blob.core.windows.net/universal-nightly?restype=container&comp=list').Substring(3)
$MaxBlob = $null
foreach($blob in $Xml.EnumerationResults.Blobs.Blob.Where({$_.Url.Contains('win7') -and $_.Url.Contains(".zip")}))
{
if ($null -eq $MaxBlob -or ([int]$blob.Name.Split('/')[0]) -gt ([int]$MaxBlob.Name.Split('/')[0]))
{
$MaxBlob = $blob
}
}
Invoke-WebRequest $MaxBlob.Url -OutFile "$PSScriptRoot\Universal.zip"
Expand-Archive -Path "$PSScriptRoot\Universal.zip" -Destination "$PSScriptRoot\Universal"
Get-ChildItem "$PSScriptRoot\Universal" -Recurse | Unblock-File
}
task RunTests {
New-Item C:\ProgramData\UniversalAutomation -ItemType Directory
Copy-Item "$PSScriptRoot\assets\Repository" C:\ProgramData\UniversalAutomation -Recurse
Import-Module "$PSScriptRoot\Universal\Universal.psd1"
$Process = Start-Process "$PSScriptRoot\Universal\Universal.Server.exe" -PassThru
while($true)
{
try
{
Invoke-RestMethod "http://localhost:5000/api/v1/alive"
break;
}
catch
{
}
}
try
{
Invoke-RestMethod "http://localhost:5000/api/v1/signin" -Method Post -Body (@{
Username = "admin"
Password = "admin"
} | ConvertTo-Json) -SessionVariable Session -ContentType "application/json"
$ENV:TestAppToken = (Invoke-RestMethod "http://localhost:5000/api/v1/apptoken/grant" -Method GET -WebSession $Session).Token
Connect-PSUServer -AppToken $ENV:TestAppToken -ComputerName "http://localhost:5000"
Set-Location $PSScriptRoot
$Results = Invoke-Pester -PassThru
if ($Results.Result -ne 'Passed')
{
throw "Tests failed!"
}
}
finally
{
Stop-Process $Process
}
}
task CleanAndRun Clean, RunTests
task . Clean, DownloadNightly, RunTests
Exécuter des tests Pester dans GitHub Actions
Dans notre environnement, nous utilisons GitHub Actions pour les pipelines CI et CD. Nous avons un agent auto-hébergé dédié aux tests d’intégration. Dans le dépôt Universal, nous avons configuré un fichier YAML de workflow GitHub Actions pour exécuter nos tests sur cet agent. Le workflow s’exécute chaque matin et peut être déclenché manuellement.
Nous appelons Invoke-Build pour lancer les tests d’intégration. Si un test échoue, une exception est levée et le workflow échoue aussi.
name: Integration Tests
on:
schedule:
- cron: "0 4 * * *"
workflow_dispatch:
jobs:
build:
name: Build
runs-on: self-hosted
steps:
- uses: actions/checkout@v1
- name: Run Integration Test
run: Invoke-Build -File .\test\integration-test.ps1
shell: pwsh
La sortie de l’Action indique quels tests ont réussi et lesquels ont échoué. Voici le résultat d’une de nos suites de tests.
Prêt à construire? Téléchargez PowerShell Universal.

Adam Driscoll