Aprenda a escribir pruebas contra PowerShell Universal con Pester 5.
Instalar requisitos previos
En esta entrada usaremos InvokeBuild y Pester. Deberá instalar estos módulos para seguir el ejemplo.
Install-Module Pester
Install-Module InvokeBuild
Descargar PowerShell Universal
El primer paso es descargar e instalar la versión de PowerShell Universal contra la que desea probar su configuración. Puede hacerlo de dos formas. Puede usar Invoke-WebRequest directamente para descargar el ZIP, o usar Install-PSUServer para descargar el MSI e instalar el servicio.
En este ejemplo usaré Invoke-WebRequest porque descargaré la compilación nightly. El script siguiente lee el almacenamiento blob nightly, encuentra la última compilación, descarga el ZIP, lo extrae y desbloquea los archivos.
[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
También puede adaptar el script anterior para descargar una versión publicada.
Invoke-WebRequest "https://imsreleases.blob.core.windows.net/universal/production/2.5.4/Universal.win7-x64.2.5.4.zip" -OutFile "$PSScriptRoot\Universal.zip"
Configurar PowerShell Universal
Una vez descargado PowerShell Universal, podemos configurar PSU con archivos de configuración ya preparados. En nuestra suite de pruebas de integración tenemos archivos .ps1 que cargamos en el directorio de configuración antes de arrancar el servidor.
Nuestros archivos de prueba cubren muchas permutaciones de configuración de PSU. Este es un fragmento de nuestro 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
A continuación tomamos esta configuración y la desplegamos en el directorio C:\ProgramData\UniversalAutomation\Repository.
New-Item C:\ProgramData\UniversalAutomation -ItemType Directory
Copy-Item "$PSScriptRoot\assets\Repository" C:\ProgramData\UniversalAutomation -Recurse
Preparar un test de Pester
A continuación podemos arrancar el servidor PSU y dejarlo listo para la suite de Pester. El script siguiente inicia el servidor PSU, espera a que esté activo, inicia sesión y concede un app token que usaremos más adelante en las pruebas.
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
}
Escribir un test de Pester
El siguiente paso es ejecutar los scripts de validación con Pester. Esta entrada se escribió contra Pester 5.3.1.
Aquí hay un subconjunto de las pruebas que ejecutamos para scripts. Aprovechamos una prueba basada en datos que nos permite ejecutar contra varios entornos al pasar una matriz -ForEach al bloque Describe.
Todas las pruebas de este ejemplo se ejecutarán tres veces, una por cada entorno. También usamos el bloque BeforeAll para establecer el entorno antes de que se ejecuten las pruebas de cada entorno.
Por último, cada bloque It valida una funcionalidad concreta con cmdlets como 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'
}
}
}
Empaquetar en un script InvokeBuild
Con el marco de pruebas y las pruebas listas, podemos envolverlos en un script InvokeBuild. Así es más fácil llamar a cada parte del flujo.
Este es el ejemplo completo. Hemos definido tres tareas de build. Una limpia las ejecuciones de pruebas anteriores. La segunda descarga y extrae la compilación nightly. La última ejecuta la suite de pruebas.
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
Ejecutar pruebas Pester en GitHub Actions
En nuestro entorno usamos GitHub Actions para las canalizaciones de CI y CD. Tenemos un agente autohospedado dedicado a las pruebas de integración. En el repositorio Universal hemos configurado un archivo YAML de flujo de GitHub Actions para ejecutar las pruebas en ese agente. El flujo se ejecuta cada mañana y se puede desencadenar de forma manual.
Llamamos a Invoke-Build para ejecutar las pruebas de integración. Si una prueba falla, se lanza una excepción y el flujo también falla.
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 salida de la Action indica qué pruebas se superaron y cuáles fallaron. Este es el resultado de una de nuestras suites.
¿Listo para crear? Descargue PowerShell Universal.

Adam Driscoll