Structurer les tests PowerShell avec Pester — une méthode pratique pour rendre les scripts d'exploitation plus difficiles à casser

· Mis à jour le: · · PowerShell, Pester, Windows, Tests, Automatisation, CI, Réutilisation de l'existant

1. Ce qu’il faut comprendre en premier

Un script PowerShell commence en général comme l’automatisation d’une petite tâche.

Collecter des fichiers. Rechercher dans des journaux. Générer un CSV. Déplacer d’anciens fichiers. Vérifier l’état d’un service.

Tant qu’il s’agit de quelques dizaines de lignes, on peut vérifier le fonctionnement à l’œil nu. Mais à mesure que le script continue d’être utilisé en production, des évolutions de ce genre s’y ajoutent :

  • Ajouter des dossiers cibles
  • Ajouter des conditions d’exclusion
  • Modifier les colonnes du CSV
  • Archiver avant suppression
  • Exécuter depuis le Planificateur de tâches ou la CI
  • Notifier en cas d’erreur

À ce stade, « ça a fonctionné une fois sur mon poste » ne suffit plus.

Le côté effrayant de PowerShell est indissociable de sa commodité. Les opérations de simple lecture peuvent s’essayer sans risque, mais pour des traitements comme la suppression, le déplacement, l’écrasement, le redémarrage d’un service ou le changement de droits, la moindre erreur de condition peut se transformer en incident.

C’est là qu’intervient Pester, le framework de test pour PowerShell. Plutôt que de couvrir l’ensemble des fonctionnalités de Pester, cet article détaille une démarche de « structuration des tests » qui rend, en pratique, les scripts PowerShell existants plus difficiles à casser.

Les tests PowerShell ne servent pas seulement à écrire du code propre. Ce sont des outils pour réduire l’inquiétude avant un changement et pour vérifier, preuves à l’appui, après ce changement.

Le code présenté dans cet article est publié sur GitHub sous la forme d’un ensemble complet d’exemples exécutables avec Invoke-Pester (le script testé, les tests Pester et un script d’exécution pour la CI).

pester-powershell-test-maintenance - komurasoft-blog-samples (GitHub)

2. Que protège-t-on avec Pester ?

Ajouter Pester ne rend pas tout sûr automatiquement. La première chose à décider est : « que doivent protéger les tests ? »

Pour les scripts d’exploitation PowerShell, prioriser ces quatre points est généralement efficace.

Ce qu’on veut protéger Ce que le test vérifie
Logique de condition Quels fichiers, lignes, utilisateurs ou services sont ciblés
Forme de la sortie Noms de colonnes du CSV, propriétés de la valeur de retour, nombre d’éléments
Étape avant une opération dangereuse Si les cibles de suppression, de déplacement ou d’arrêt correspondent à l’intention
Dépendances externes Comportement du système de fichiers, des API, de l’exécution de commandes, des dates/heures, des variables d’environnement

Ce qu’on veut tester en premier, en particulier, n’est pas le traitement de suppression lui-même, mais le traitement qui sélectionne les cibles à supprimer.

Par exemple, pour un script qui supprime d’anciens journaux, ne commencez pas par tester Remove-Item : testez d’abord « quels journaux sont sélectionnés comme cibles ».

Cette séparation facilite grandement les tests.

Fonction qui collecte les cibles
  ↓
Étape qui vérifie et enregistre les cibles
  ↓
Étape de modification : déplacement, suppression, notification, etc.

Structurer les tests PowerShell ne consiste pas à refondre immédiatement l’ensemble d’un script existant. Commencez par extraire dans une fonction la partie de décision qui précède un traitement dangereux, puis vérifiez sa valeur de retour avec Pester.

3. Aligner les versions

Cet article part du principe que vous utilisez Pester v5.

Sur d’anciens environnements Windows, Pester peut déjà être installé, mais dans sa branche v3. Plutôt que d’utiliser tel quel ce qui est déjà présent dans l’environnement, commencez par vérifier la version.

Get-Module Pester -ListAvailable |
  Sort-Object Version -Descending |
  Select-Object Name, Version, Path

Pour une nouvelle installation, installez-le depuis la PowerShell Gallery.

Install-Module -Name Pester -Scope CurrentUser -Force -SkipPublisherCheck
Import-Module Pester
Get-Module Pester

En travail d’équipe, vérifiez que la version de Pester ne diverge pas entre le poste de développement, le serveur de build et l’environnement d’exécution des tâches.

Une source fréquente de confusion dans les tests PowerShell n’est pas un problème de code, mais un écart de version du test runner.

En particulier, d’anciens articles ou notes internes peuvent encore contenir une syntaxe antérieure à Pester v4. Si vous mettez en place de nouveaux tests, s’aligner sur la syntaxe v5 les rendra plus faciles à relire par la suite.

4. Décider où placer les fichiers

Avec Pester, il est courant de nommer les fichiers de test *.Tests.ps1.

Voici la disposition minimale :

scripts/
  Get-OldLogFile.ps1
  Get-OldLogFile.Tests.ps1

Pour un projet un peu plus grand, séparez src et tests :

src/
  public/
    Get-OldLogFile.ps1
    Remove-OldLogFile.ps1

tests/
  public/
    Get-OldLogFile.Tests.ps1
    Remove-OldLogFile.Tests.ps1

Les deux conviennent. Ce qui compte, c’est de fixer une convention.

  • Un fichier de test par fonction
  • Les noms de fichiers de test se terminent par .Tests.ps1
  • Charger le code testé toujours de la même façon
  • Ne pas trop mélanger tests unitaires et tests d’intégration

Au départ, placer le .ps1 cible juste à côté de son .Tests.ps1 suffit amplement.

5. Exécuter un test minimal

Préparons d’abord une fonction simple, Get-OldLogFile.ps1.

function Get-OldLogFile {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Path,

        [int] $Days = 30,

        [string] $Filter = '*.log',

        [datetime] $Now = (Get-Date)
    )

    if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
        throw "Folder not found: $Path"
    }

    $limit = $Now.AddDays(-1 * $Days)

    Get-ChildItem -LiteralPath $Path -Filter $Filter -File |
        Where-Object { $_.LastWriteTime -lt $limit } |
        Sort-Object -Property LastWriteTime |
        Select-Object FullName, Name, Length, LastWriteTime
}

Ici, pour faciliter les tests, $Now est reçu comme paramètre.

Si la fonction appelle directement Get-Date à chaque exécution, le résultat varie selon le jour où le test est lancé. En transformant la date en paramètre, on peut fixer une condition comme « les fichiers plus anciens que 30 jours au 1er juin 2026 » et la tester telle quelle.

Ensuite, écrivons les tests dans Get-OldLogFile.Tests.ps1.

BeforeAll {
    . $PSScriptRoot\Get-OldLogFile.ps1
}

Describe 'Get-OldLogFile' {
    BeforeEach {
        $script:Root = Join-Path $TestDrive 'logs'
        New-Item -ItemType Directory -Path $script:Root -Force | Out-Null

        $oldLog = Join-Path $script:Root 'old.log'
        $newLog = Join-Path $script:Root 'new.log'
        $oldTxt = Join-Path $script:Root 'old.txt'

        Set-Content -LiteralPath $oldLog -Value 'old log' -Encoding UTF8
        Set-Content -LiteralPath $newLog -Value 'new log' -Encoding UTF8
        Set-Content -LiteralPath $oldTxt -Value 'old text' -Encoding UTF8

        (Get-Item -LiteralPath $oldLog).LastWriteTime = [datetime]'2026-05-01T00:00:00'
        (Get-Item -LiteralPath $newLog).LastWriteTime = [datetime]'2026-05-31T00:00:00'
        (Get-Item -LiteralPath $oldTxt).LastWriteTime = [datetime]'2026-05-01T00:00:00'
    }

    It 'ne retourne que les fichiers .log plus anciens que le nombre de jours indiqué' {
        $result = Get-OldLogFile `
            -Path $script:Root `
            -Days 30 `
            -Now ([datetime]'2026-06-01T00:00:00')

        $result | Should -HaveCount 1
        $result[0].Name | Should -Be 'old.log'
    }

    It 'échoue si le dossier est introuvable' {
        { Get-OldLogFile -Path (Join-Path $TestDrive 'missing') } |
            Should -Throw
    }
}

Exécutons-le.

Invoke-Pester -Output Detailed .\Get-OldLogFile.Tests.ps1

Le $TestDrive utilisé ici est une zone temporaire fournie par Pester pour les tests. Au lieu de toucher au vrai C:\Logs ou à un dossier partagé, on travaille uniquement avec des fichiers créés à l’intérieur du test. Pour les scripts PowerShell impliquant des opérations sur les fichiers, prendre l’habitude d’utiliser $TestDrive en premier lieu est la démarche sûre.

6. Écrire les noms de test comme des spécifications

La chaîne de caractères que l’on écrit dans le It de Pester n’est pas une simple description : pour quiconque la relira plus tard, elle devient une petite spécification.

Par exemple, un nom comme celui-ci est un peu faible :

It 'fonctionne' {
    # ...
}

On ne sait pas ce qui est censé fonctionner.

En pratique, un nom qui inclut la condition et le résultat attendu est bien plus facile à lire.

It 'ne retourne que les fichiers .log plus anciens que le nombre de jours indiqué' {
    # ...
}

It 'exclut les fichiers exactement à la date limite' {
    # ...
}

It 'échoue si le dossier est introuvable' {
    # ...
}

Un bon nom de test se révèle utile au moment d’un échec. Quand le journal de la CI affiche ceci, on comprend immédiatement ce qui a cassé :

[-] Get-OldLogFile.exclut les fichiers exactement à la date limite

Le nom d’un test est une note laissée à son futur soi.

7. Ajouter une condition limite

Le Get-OldLogFile vu plus haut détermine qu’un fichier est ancien avec cette condition :

$_.LastWriteTime -lt $limit

Comme il s’agit de -lt, un fichier dont la date correspond exactement à la date limite est exclu.

Cette décision est minime, mais elle compte en pratique, car le nombre de fichiers concernés change selon qu’on retient « plus ancien que 30 jours » ou « 30 jours ou plus, inclus ».

Ajoutons la condition limite aux tests.

It 'exclut les fichiers exactement à la date limite' {
    $border = Join-Path $script:Root 'border.log'
    Set-Content -LiteralPath $border -Value 'border log' -Encoding UTF8
    (Get-Item -LiteralPath $border).LastWriteTime = [datetime]'2026-05-02T00:00:00'

    $result = Get-OldLogFile `
        -Path $script:Root `
        -Days 30 `
        -Now ([datetime]'2026-06-01T00:00:00')

    $result.Name | Should -Not -Contain 'border.log'
}

Il ne s’agit pas d’écrire le plus de tests possible. Mais les traitements comportant une limite — dates, valeurs numériques, nombre d’éléments, droits, motifs de noms de fichiers — sont ceux où les tests apportent le plus de valeur.

8. Fixer la forme de la valeur de retour

Dans les scripts PowerShell, la forme de la valeur de retour peut changer sans qu’on s’en aperçoive.

Au départ, la fonction retournait directement un FileInfo. Puis un Select-Object a été ajouté en cours de route. Puis les noms de colonnes ont été modifiés pour la sortie CSV.

Ce genre de changement affecte le traitement en aval ; tester les propriétés de la valeur de retour permet donc de détecter un changement inattendu.

It 'retourne les propriétés utilisées par le traitement suivant' {
    $result = Get-OldLogFile `
        -Path $script:Root `
        -Days 30 `
        -Now ([datetime]'2026-06-01T00:00:00')

    $propertyNames = $result[0].PSObject.Properties.Name

    $propertyNames | Should -Contain 'FullName'
    $propertyNames | Should -Contain 'Name'
    $propertyNames | Should -Contain 'Length'
    $propertyNames | Should -Contain 'LastWriteTime'
}

Pour une fonction dont la sortie alimente un export CSV ou un rapport, les noms de colonnes font partie de la spécification, tout autant que les valeurs.

Vérifiez non seulement que « ça fonctionne », mais aussi que « le résultat est renvoyé dans la forme attendue par l’étape suivante ».

9. Séparer la suppression de la sélection des cibles

Passons maintenant au traitement de suppression. Commençons par un mauvais exemple.

Get-ChildItem C:\Logs -Filter *.log -File |
    Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-30) } |
    Remove-Item -Force

C’est court et pratique, mais difficile à tester. Comme la sélection des cibles et la suppression sont enchaînées dans un seul pipeline, on ne sait plus vraiment quoi vérifier.

En pratique, on sépare les deux comme ceci :

function Remove-OldLogFile {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)]
        [string] $Path,

        [int] $Days = 30,

        [datetime] $Now = (Get-Date)
    )

    $targets = Get-OldLogFile -Path $Path -Days $Days -Now $Now

    foreach ($target in $targets) {
        if ($PSCmdlet.ShouldProcess($target.FullName, 'Remove old log file')) {
            Remove-Item -LiteralPath $target.FullName -Force
        }
    }
}

Ici, SupportsShouldProcess est ajouté pour que la fonction puisse elle-même accepter -WhatIf.

Remove-OldLogFile -Path C:\Logs -Days 30 -WhatIf

Pour les fonctions PowerShell qui suppriment des éléments, il est plus sûr de permettre autant que possible une répétition à blanc avec -WhatIf.

10. Remplacer les opérations dangereuses par Mock

Avec Pester, Mock permet de remplacer l’exécution réelle d’une commande.

Il n’est pas nécessaire d’exécuter réellement Remove-Item pour tester la logique de suppression.

A-t-elle été appelée dans les situations où elle doit l’être ? N’a-t-elle pas été appelée dans les situations où elle ne doit pas l’être ?

Vérifier cela suffit.

Voici un exemple de Remove-OldLogFile.Tests.ps1 :

BeforeAll {
    . $PSScriptRoot\Get-OldLogFile.ps1
    . $PSScriptRoot\Remove-OldLogFile.ps1
}

Describe 'Remove-OldLogFile' {
    It 'appelle Remove-Item pour les anciens fichiers journaux' {
        Mock Get-OldLogFile {
            [pscustomobject]@{
                FullName      = 'C:\Logs\old.log'
                Name          = 'old.log'
                Length        = 10
                LastWriteTime = [datetime]'2026-05-01'
            }
        }

        Mock Remove-Item {}

        Remove-OldLogFile `
            -Path 'C:\Logs' `
            -Days 30 `
            -Now ([datetime]'2026-06-01')

        Should -Invoke Remove-Item `
            -Times 1 `
            -Exactly `
            -ParameterFilter { $LiteralPath -eq 'C:\Logs\old.log' }
    }

    It 'ne fait pas appel à Remove-Item avec WhatIf' {
        Mock Get-OldLogFile {
            [pscustomobject]@{
                FullName      = 'C:\Logs\old.log'
                Name          = 'old.log'
                Length        = 10
                LastWriteTime = [datetime]'2026-05-01'
            }
        }

        Mock Remove-Item {}

        Remove-OldLogFile `
            -Path 'C:\Logs' `
            -Days 30 `
            -Now ([datetime]'2026-06-01') `
            -WhatIf

        Should -Invoke Remove-Item -Times 0
    }
}

Dans ces tests, Get-OldLogFile comme Remove-Item sont tous deux mockés, si bien que le vrai C:\Logs\old.log n’a même pas besoin d’exister. Ce qui est examiné, c’est la logique de décision de Remove-OldLogFile :

  • S’il y a des cibles, appeler Remove-Item
  • Avec -WhatIf, ne pas appeler Remove-Item
  • Lors de l’appel, transmettre le chemin voulu

Plus une opération est dangereuse, plus il est prudent de tester les conditions de son appel plutôt que l’exécution elle-même.

11. Ne pas abuser de Mock

Mock est pratique, mais en abuser fait perdre de la valeur aux tests, car tout mocker éloigne trop du comportement réel de PowerShell.

Voici quelques repères.

Traitement Recommandation
Dates Les fixer via un paramètre
Création de fichiers Utiliser $TestDrive
Suppression, déplacement Vérifier avec Mock et -WhatIf
Appels à une API Web Mock-er Invoke-RestMethod et similaires
Envoi d’e-mails, notifications Mock-er la commande d’envoi
Lecture/écriture de CSV Créer de petits fichiers réels dans $TestDrive

Mocker jusqu’à la lecture et l’écriture des fichiers peut faire passer à côté de vrais problèmes d’encodage de caractères, de fins de ligne ou de noms de colonnes.

À l’inverse, des traitements comme la suppression, les notifications, les API externes ou l’arrêt de services ne devraient vraiment pas être exécutés pour de bon.

Séparez « les endroits où utiliser le vrai comportement » des « endroits à mocker ».

12. Rendre les scripts existants testables

Introduire Pester modifie un peu la façon d’écrire les scripts existants. Mais il n’est pas nécessaire de se lancer d’emblée dans un grand changement de conception : des retouches de cette ampleur suffisent pour commencer.

Avant retouche

$limit = (Get-Date).AddDays(-30)

Get-ChildItem C:\Logs -Filter *.log -File |
    Where-Object { $_.LastWriteTime -lt $limit } |
    Remove-Item -Force

Après retouche

function Get-OldLogFile {
    param(
        [string] $Path,
        [int] $Days = 30,
        [datetime] $Now = (Get-Date)
    )

    $limit = $Now.AddDays(-1 * $Days)

    Get-ChildItem -LiteralPath $Path -Filter *.log -File |
        Where-Object { $_.LastWriteTime -lt $limit }
}

function Remove-OldLogFile {
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [string] $Path,
        [int] $Days = 30,
        [datetime] $Now = (Get-Date)
    )

    Get-OldLogFile -Path $Path -Days $Days -Now $Now |
        ForEach-Object {
            if ($PSCmdlet.ShouldProcess($_.FullName, 'Remove old log file')) {
                Remove-Item -LiteralPath $_.FullName -Force
            }
        }
}

Les changements ne sont pas considérables.

  • La date est devenue un paramètre
  • La sélection des cibles est devenue une fonction
  • Le traitement de suppression est devenu une fonction séparée
  • SupportsShouldProcess a été ajouté

Cela seul rend le code bien plus facile à tester.

Pour structurer les tests PowerShell, plutôt que de partir de considérations théoriques de conception, il est plus efficace de rendre remplaçables depuis l’extérieur « la date », « le chemin », « les commandes externes » et « les opérations de modification ».

13. Décider des catégories de tests

Avec Pester, on peut ajouter des tags à Describe, Context et It.

Par exemple, on sépare les tests unitaires rapides des tests d’intégration qui touchent l’environnement réel.

Describe 'Get-OldLogFile' -Tag 'Unit' {
    It 'ne retourne que les fichiers .log plus anciens que le nombre de jours indiqué' {
        # Test rapide utilisant TestDrive
    }
}

Describe 'Log maintenance smoke test' -Tag 'Smoke' {
    It 'peut lire le vrai dossier de journaux' {
        Test-Path -LiteralPath 'C:\Logs' | Should -BeTrue
    }
}

Exécutons uniquement les tests unitaires.

Invoke-Pester -TagFilter Unit

Excluons les tests lents ou dépendants de l’environnement.

Invoke-Pester -ExcludeTagFilter Slow, RequiresAdmin, Network

Sur le terrain, vouloir exécuter tous les tests à chaque fois peut s’avérer intenable dans la durée.

Commencez par faire des tests rapides et sans effet de bord la norme, et séparez par tags les tests dépendants de l’environnement pour les exécuter seulement quand c’est nécessaire.

14. Exécuter en CI

Pester est déjà utile en exécution locale, mais si une équipe gère les scripts collectivement, pouvoir l’exécuter en CI apporte une tranquillité d’esprit supplémentaire.

Préparons par exemple un fichier comme tools/Invoke-ProjectTests.ps1 :

$ErrorActionPreference = 'Stop'

Import-Module Pester

$config = New-PesterConfiguration

$config.Run.Path = @(
    Join-Path $PSScriptRoot '..\tests'
)

$config.Run.Exit = $true
$config.Output.Verbosity = 'Detailed'

$config.TestResult.Enabled = $true
$config.TestResult.OutputFormat = 'JUnitXml'
$config.TestResult.OutputPath = Join-Path $PSScriptRoot '..\test-results.xml'

$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = @(
    Join-Path $PSScriptRoot '..\src'
)
$config.CodeCoverage.OutputPath = Join-Path $PSScriptRoot '..\coverage.xml'

Invoke-Pester -Configuration $config

Côté CI, on exécute ce script :

pwsh -NoProfile -File .\tools\Invoke-ProjectTests.ps1

Le point important est de ne pas trop écrire de configuration spécifique à la CI à l’intérieur des fichiers de test eux-mêmes.

Les fichiers de test sont l’endroit où l’on écrit des spécifications. Le format de sortie pour la CI, la couverture de code, le code de sortie, etc. sont plus faciles à organiser en les regroupant dans le script d’exécution.

15. Considérer la couverture comme une carte, pas comme un objectif

Pester peut aussi produire la couverture de code. Mais mieux vaut ne pas trop courir après le chiffre dès le départ, car la couverture n’est pas la qualité des tests elle-même.

Par exemple, appeler une seule fois la fonction d’extraction des cibles de suppression suffit à faire passer ces lignes comme « couvertes ». Mais si les conditions limites et les conditions d’exclusion n’ont pas été vérifiées, cela n’apporte aucune assurance réelle en pratique.

Voici comment utiliser la couverture :

  • Repérer les fonctions qui ne sont jamais exercées
  • Repérer les branches importantes qui n’ont aucun test
  • Prioriser en partant des scripts les plus fréquemment modifiés
  • Conserver, via la CI, une trace de l’exécution des tests

Plutôt que de faire monter le chiffre, regardez si « les décisions importantes sont testées ».

16. Ordre d’introduction des tests dans les scripts existants

Pour introduire Pester dans un ensemble de scripts PowerShell existants, il est plus facile d’avancer sans chercher à tout couvrir d’un coup. Voici un ordre recommandé.

1. Choisir les scripts dont l’échec ferait mal

Au départ, ce type de scripts convient bien :

  • Ils incluent suppression, déplacement ou écrasement
  • Ils tournent tous les jours ou tous les mois
  • Ils figurent dans une procédure documentée mais restent la spécialité d’une seule personne
  • Ils ont déjà connu une erreur de condition par le passé
  • Leur CSV de sortie est utilisé par d’autres activités métier

Commencez par ce qui est utile mais poserait problème s’il venait à casser.

2. Extraire uniquement la partie lecture dans des fonctions

Ce qu’on teste en premier, ce n’est pas le traitement de modification, mais le traitement de lecture.

Lire les journaux
Filtrer les cibles
Compter les éléments
Mettre en forme pour le CSV

Cette partie se teste facilement avec $TestDrive, et les accidents y sont peu probables.

3. Passer la date et le chemin depuis l’extérieur

Quand la date ou le chemin sont figés en dur, les tests deviennent difficiles.

# À éviter
$root = 'C:\Logs'
$limit = (Get-Date).AddDays(-30)

Voici une forme facile à tester.

param(
    [string] $Path,
    [datetime] $Now = (Get-Date)
)

Le simple fait de pouvoir passer les valeurs depuis l’extérieur améliore considérablement la stabilité des tests.

4. Placer les opérations dangereuses en dernier

Regroupez la suppression et le déplacement à la toute fin.

Construire la liste des cibles
  ↓
Consigner les cibles dans un journal
  ↓
Vérifier avec -WhatIf
  ↓
Exécuter

Vérifiez également dans le même ordre au sein des tests.

17. Difficultés courantes

Symptôme Cause Solution
Passe en local mais échoue en CI Le répertoire courant diffère Se baser sur $PSScriptRoot
Le résultat varie selon le jour Get-Date est utilisé directement Prévoir un paramètre du type -Now
Le test risque de supprimer de vrais fichiers Un vrai dossier est utilisé Utiliser $TestDrive et Mock
Le Mock n’a aucun effet Une frontière de module ou de portée diffère Vérifier -ModuleName et la méthode de chargement
On ne sait pas jusqu’où tester La spécification n’est pas répartie en fonctions Séparer sélection des cibles, mise en forme et modification
Les tests sont lents Un service externe ou le réseau est sollicité Mock-er les dépendances externes dans les tests unitaires
Le nom du test ne dit rien Un nom du type It 'works' Inclure la condition et le résultat attendu dans le nom

Ce qui ressemble à un problème de Pester provient souvent, en réalité, de la structure même du script.

Les parties difficiles à tester sont généralement aussi celles qui cassent le plus facilement en exploitation.

18. Règles à fixer pour la mise en place des tests

Si une équipe gère des scripts PowerShell collectivement, fixez des règles avant de vous soucier des détails de style.

En voici des exemples.

  • Les fichiers de test sont nommés *.Tests.ps1
  • Le code testé est chargé depuis $PSScriptRoot
  • Les tests d’opérations sur les fichiers utilisent $TestDrive
  • La suppression, le déplacement, les notifications et les appels d’API sont mockés par principe
  • Les dates sont paramétrées pour pouvoir être fixées
  • Describe ou It porte des tags comme Unit, Smoke, RequiresAdmin
  • La CI exécute Unit par défaut
  • Les fonctions de modification reçoivent SupportsShouldProcess chaque fois que possible
  • Les anomalies passées sont conservées sous forme de tests de non-régression

Trop de règles finissent par ne plus être suivies. Au départ, ces trois-là suffisent amplement :

Utiliser TestDrive
Fixer la date
Mocker les opérations dangereuses

Respecter uniquement ces trois règles rend déjà les tests PowerShell nettement plus stables.

19. Décider aussi de ce qu’on ne teste pas

Dans la mise en place des tests, « ce qu’on ne teste pas » compte autant que « ce qu’on teste ».

Par exemple, mieux vaut ne pas s’acharner à vérifier ces points-ci dans des tests unitaires :

  • Que Get-ChildItem de Windows lui-même fonctionne correctement
  • Que Remove-Item supprime réellement les fichiers
  • Le comportement interne des commandes standard de PowerShell
  • Qu’une API externe réponde toujours
  • Qu’un partage réseau soit toujours disponible

Ce qu’il faut tester, ce sont vos propres décisions.

  • Selon quelle condition un élément devient une cible
  • Quel chemin est transmis
  • Quelles colonnes sont produites en sortie
  • Comment un échec est géré
  • Si une opération dangereuse peut être répétée à blanc

Séparez les endroits où vous faites confiance aux commandes standard de ceux où vous protégez votre propre logique.

20. Conclusion

PowerShell est un outil pratique qui permet d’automatiser rapidement des tâches quotidiennes. Mais un script utilisé longtemps en production voit sa responsabilité s’alourdir peu à peu. Ce qui n’était au départ qu’une commande d’une ligne pour un usage personnel finit par devenir un traitement d’exploitation qui tourne tous les jours et affecte le travail d’autres personnes ainsi que les données métier.

Structurer les tests avec Pester, c’est le travail qui protège le script à mesure de cette évolution.

Récapitulons les points clés.

  • Tester d’abord la sélection des cibles
  • Rendre la date et le chemin transmissibles depuis l’extérieur
  • Confiner les opérations sur les fichiers dans $TestDrive
  • Mock-er la suppression, le déplacement, les notifications et les appels d’API
  • Rendre les fonctions de modification répétables à blanc avec -WhatIf
  • Écrire les noms de test de façon à ce qu’on puisse les lire comme des spécifications
  • En CI, faire tourner en premier les tests rapides et sans effet de bord

L’exploitation sûre de PowerShell ne consiste pas à mettre en place d’un coup un grand dispositif.

Découper en petites fonctions. Écrire de petits tests. Rendre possible une vérification avant tout traitement dangereux.

Cette accumulation fait progressivement passer PowerShell d’un « script pratique mais un peu effrayant » à un « outil métier qu’on peut vérifier à chaque changement ».

Liens de référence

Articles connexes

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Que faut-il tester en premier avec Pester ?
Ne commencez pas par le traitement de suppression lui-même, mais par le traitement qui sélectionne les cibles à supprimer (la sélection des cibles). Pour les scripts d'exploitation, il est efficace de prioriser ces quatre points : « la logique de condition », « la forme de la sortie », « l'étape juste avant une opération dangereuse » et « les dépendances externes ». Il n'est pas nécessaire de refondre immédiatement l'intégralité d'un script existant : commencez par extraire dans une fonction la logique de décision qui précède une opération dangereuse, puis vérifiez sa valeur de retour avec Pester.
Qu'est-ce que $TestDrive dans Pester ?
$TestDrive est une zone temporaire fournie par Pester pour les tests. Il permet de vérifier des opérations sur les fichiers en utilisant uniquement des fichiers créés à l'intérieur du test, sans toucher au vrai C:\Logs ni à un dossier partagé. Pour les tests de scripts PowerShell impliquant des opérations sur les fichiers, prendre l'habitude d'utiliser $TestDrive dès le départ permet d'éviter des accidents comme la suppression involontaire de vrais fichiers.
Jusqu'où faut-il utiliser Mock dans Pester ?
Remplacez par un Mock les opérations qu'il ne faut jamais exécuter réellement, comme la suppression, le déplacement, les appels à une API Web, ou l'envoi d'e-mails et de notifications ; fixez les dates via un paramètre ; et pour la création de fichiers ou la lecture/écriture de CSV, il est préférable de créer de petits fichiers réels dans $TestDrive. Si vous mockez absolument tout, vous vous éloignez trop du comportement réel de PowerShell et vous risquez de passer à côté de problèmes d'encodage de caractères, de fins de ligne ou de noms de colonnes. Le point clé est donc de séparer « les endroits où utiliser le vrai comportement » des « endroits à mocker ».
Pourquoi un test Pester qui passe en local échoue-t-il en CI ?
La cause la plus fréquente est une différence de répertoire courant, que l'on résout en chargeant le script testé à partir de $PSScriptRoot. Si les résultats varient selon le jour, c'est généralement parce que Get-Date est utilisé directement dans le code : fixez la date via un paramètre du type -Now. Si un Mock ne semble pas prendre effet, suspectez une différence de frontière de module ou de portée (scope), et vérifiez -ModuleName ainsi que la méthode de chargement. Ce qui ressemble à un problème de Pester provient en réalité souvent de la structure même du script.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog