Pide tu presupuesto ya!
31st January 2025
Si te encuentras pasando más tiempo manteniendo tus pruebas de Pester que crear otras nuevas, esta publicación es para ti. En esta publicación, compartiré un proyecto en el que he estado trabajando con devoluciones.
Tuvimos un problema en las devoluciones. El buque insignia del módulo de PowerShell Devolutions.Powershell carecía de pruebas de Pester. Lo sé, lo sé. Tuvieron pruebas de C#, pero esa es otra historia.
Como consultor de devoluciones centradas en PowerShell, me pidieron que creara un conjunto de pruebas Pester para usar en la tubería de CI/CD y ejecutar antes del despliegue de producción. No hay problema, pensé. He usado el módulo varias veces, y no podría ser tan difícil. Me equivoqué.
Sin ninguna solicitud específica, solo “construir algunas pruebas”, me propuse crear pruebas para todo el módulo, ¡solo para descubrir que tenía casi 500 comandos! Esto iba a tomar un tiempo.
Como siempre quise construir soluciones no solo para hoy, sino a largo plazo, no quería arrojar un montón de pruebas a un solo guión y llamarlo un día. ¡Ese guión sería enorme!
En cambio, antes de escribir una sola prueba, quería diseñar un marco que pudiera usar que lo haría:
Estos requisitos conducen al marco de prueba Pester para las devoluciones.
Si desea usar este marco, no puedo garantizar que funcione al 100% a menos que esté trabajando en el mismo entorno que soy. Para usar este marco, utilicé:
Esto seguirá funcionando en Windows PowerShell y versiones anteriores de Pester V5, ¡pero no hay garantías!
Este marco consta de cuatro componentes: un script de llamadas, varios scripts de definición de prueba, un script opcional para almacenar funciones auxiliares y opcional before all / before each guiones. Cada uno de estos componentes está estructurado así en el sistema de archivos:
📁 root/
📄 caller.tests.ps1
📄 helper_functions.ps1
📁 test_definitions/
📁 group1/
📄 beforeall.tests.ps1
📄 beforeeach.tests.ps1
📄 core.tests.ps1
📄 subgroup.tests.ps1
Cuando invocas el script de la persona que llama:
Invoke-Pester -Path root/caller.tests.ps1
El script de la persona que llama:
beforeeach y beforeall Scripts para cada grupo.context 'group' {
context 'subgroup' {
}
}
beforeall Script una vez antes de que cualquier prueba se ejecute en un grupo.beforeeach Script antes de cada prueba en el grupo.El script de la persona que llama es el punto de invocación de Pester. Es el guión que Invoke-Pester Llamas cuando necesite invocar cualquier prueba dentro del marco.
El guión de la persona que llama se divide en las áreas X:
Una tarea del script de llamadas es encontrar todas las definiciones de prueba y para qué sirve la fase de descubrimiento de Pester. Dentro del bloque de descubrimiento de script, las definiciones de prueba se recopilan, así como cualquiera before each o before all guiones.
BeforeDiscovery {
# Initialize hashtable to store all test definitions
$tests = @{}
# Iterate through test group directories (e.g. datasources, entries)
Get-ChildItem -Path "$PSScriptRoot\test_definitions" -Directory -PipelineVariable testGroupDir | ForEach-Object {
$testGroup = $testGroupDir.BaseName
$tests[$testGroup] = @{}
# Load beforeall script for test group if it exists
# This script runs once before all tests in the group
$testGroupBeforeAllScriptPath = Join-Path -Path $testGroupDir.FullName -ChildPath 'beforeall.ps1'
if (Test-Path -Path $testGroupBeforeAllScriptPath) {
$tests[$testGroup]['beforeall'] += . $testGroupBeforeAllScriptPath
}
# Load beforeeach script for test group if it exists
# This script runs before each individual test in the group
$testGroupBeforeEachScriptPath = Join-Path -Path $testGroupDir.FullName -ChildPath 'beforeeach.ps1'
if (Test-Path -Path $testGroupBeforeEachScriptPath) {
$tests[$testGroup]['beforeeach'] += . $testGroupBeforeEachScriptPath
}
# Load all test definition files in the group directory
Get-ChildItem -Path $testGroupDir.FullName -Filter '*.ps1' -PipelineVariable testDefinitionFile | ForEach-Object {
$tests[$testGroup][$testDefinitionFile.BaseName] = @()
$tests[$testGroup][$testDefinitionFile.BaseName] += . $testDefinitionFile.FullName
}
}
}
BeforeAll BloquearEl BeforeAll Bloque se ejecuta para poner a disposición de las funciones de ayuda para el script de llamadas o las definiciones de prueba. En Pester V5, esta tarea no puede estar en BeforeDiscovery; De lo contrario, no estaría disponible para las pruebas.
# Load helper functions used across all tests
BeforeAll {
. (Join-Path $PSScriptRoot -ChildPath "_helper_functions.ps1")
}
Describe Bloque y ContextsCada configuración del entorno con la que necesita ejecutar pruebas es dividida en contextos con grupos de prueba debajo de cada uno de ellos.
Para el módulo de PowerShell de devoluciones, ya que necesitamos probar cmdlets con muchos tipos diferentes de fuentes de datosCreé contextos por fuente de datos, pero puede usar cualquier cosa aquí. Entonces, usando Pester’s ForEach Parámetro, cada carpeta de definición de prueba es un contexto, al igual que cada subgrupo. Luego, cada prueba se define debajo como it bloques.
Notar el where() método en el it bloquear. Where({ $_.environments -contains 'xxx' -or $_.environments.count -eq 0}). Aquí es donde entra en juego la estructura de cada definición. Esta parte es donde designamos qué pruebas se ejecutan en qué entorno.
# Main test container for all tests
Describe 'RDM' {
# Tests that run against an environment
Context 'Environment1' -Tag 'Environment1' {
BeforeEach {
## Do stuff to execute before each test in this content. For example, this is used to set up a specific data source for Devolutions Remote Desktop Manager
}
# Clean up
AfterAll {
}
# Run each test group against the environment
Context '<_>' -ForEach $tests.Keys -Tag ($_) {
$testGroup = $_
# Run each test subgroup (core, properties, etc)
Context '<_>' -ForEach $tests[$testGroup].Keys -Tag ($_) {
$testSubGroup = $_
# Run tests marked for this environment or all
It '<name>' -ForEach ($tests[$testGroup][$testSubGroup]).Where({ $_.environments -contains 'xxx' -or $_.environments.count -eq 0}) {
& $_.assertion
}
}
}
}
## Other environments. If you have specific configurations some tests must have, you can define them here by creating more context blocks.
}
A continuación, tienes la parte más esencial: ¡las pruebas! Las pruebas se crean en subgrupos (subgroup.tests.ps1) Dentro de cada carpeta de grupo y debe crearse como una variedad de hashtables con la siguiente estructura:
@(
@{
'name' = 'creates a thing'
'environments' = @() ## Nothing means all environments
'assertion' = {
## Teating something
$true | should -Be $true
}
}
)
Aquí, puede definir los entornos en el script de llamadas para ejecutar los scripts. Por ejemplo, si tiene un entorno para cada fuente de datos que utilicé para devoluciones RDM, mis entornos son xml, sqlliteetc.
Finalmente, tenemos el script de funciones de ayuda. Este script contiene funciones que salemos en la fuente en el BeforeAll Bloquear en el script de llamadas. Aquí es donde puede poner cualquier función que reutilice. En mi ejemplo, tengo funciones para configurar fuentes de datos y eliminarlas todas.
# Helper function to remove all entries from the current data source
# Used for cleanup in test scenarios
function Remove-AllEntries {
try {
# Get all entries and their IDs
# Using ErrorAction SilentlyContinue to handle case where no entries exist
$entries = @(Get-RDMEntry -ErrorAction SilentlyContinue)
# If no entries found, just return silently
# No cleanup needed in this case
if ($entries.Count -eq 0) {
return
}
# Delete entries one at a time
# Using foreach loop to handle errors for individual entries
foreach ($entry in $entries) {
try {
# Remove entry and refresh to ensure UI is updated
Remove-RDMEntry -ID $entry.ID -Refresh -ErrorAction Stop
} catch [System.Management.Automation.ItemNotFoundException] {
# Silently ignore if entry is already gone
# This can happen if entry was deleted by another process
continue
}
}
} catch {
# Only warn about unexpected errors
# Ignore "Connection not found" as this is expected in some cases
if ($_.Exception.Message -ne 'Connection not found.') {
Write-Warning "Error during cleanup: $_"
}
}
}
# Helper function to remove all data sources and their files
# Used for cleanup in test scenarios
function Remove-AllDatasources {
# Remove any data sources currently loaded in memory
# This ensures clean state for tests
Get-RDMDataSource | ForEach-Object { Remove-RDMDataSource -DataSource $_ }
# Delete any existing data source folders on disk
# These are identified by GUIDs in the RDM application data folder
Get-ChildItem $env:LOCALAPPDATA\Devolutions\RemoteDesktopManager |
Where-Object { $_.Name -match '^[0-9a-fA-F\-]{36}
}
¿Este marco parece útil? ¿Crees que sería beneficioso para tu organización? Si es así, aquí hay algunos consejos y preguntas para pensar.
Construir un marco de prueba de Pester escalable requiere una planificación y una organización cuidadosa, pero los beneficios valen la pena. Siguiendo la estructura descrita en este artículo, con su diseño modular, componentes reutilizables y manejo flexible del entorno, puede crear una solución de prueba robusta que crece con sus necesidades. Ya sea que esté probando módulos PowerShell, configuraciones de infraestructura o aplicaciones complejas, este marco proporciona una base sólida para mantener la calidad y la confiabilidad del código en toda su organización.
Leave a comment