Et si on découvrait Pester!

Les tests automatisés sont une étape importante de tout développement. C’est ingrat et laborieux, mais c’est une arme redoutable pour améliorer la qualité de vos livrables.

Pester est le standard de facto des tests PowerShell. C’est l’outil que nous allons découvrir aujourd’hui. Au menu:

  • Pourquoi des tests
  • Installer Pester
  • Le pattern AAA
  • Architecture d’un test
  • Test élémentaire

Pourquoi des tests?

Il existe différents types de tests. Citons par exemple:

  1. Tests unitaires
  2. Test de déploiement
  3. Tests fonctionnels
  4. analyse de code
  5. Tests de sécurité
  6. Test de résilience ou de performance

Et la liste n’est pas terminée. Pester est un outil de choix pour la plupart de ces scénarios. Je pense donc qu’il est sain d’en avoir une culture minimale si vous faites du Devops.

Outre les tests que peut-on attendre d’autre de Pester? Beaucoup de services. par exemple:

  • Ecrire des tests unitaires d’une méthode, c’est une façon de la documenter, préciser ses cas d’usage, son périmètre de façon concrète
  • Les tests unitaires sont un outil important pour contrôler le risque de régression
  • Avec les outils de bouchonnage (mocking) il est possible de tester des scénarios difficiles à produire concrètement.
    Par exemple un script qui dépend d’une contrainte tierce (date et heure spécifique…) ou qui nécessite de disposer un environnement dédié au test. Ce type d’environnement est difficile à maintenir et je préfère l’éviter.
  • Pour être testable, un code doit être écrit d’une façon qui s’y prête. Et cela implique en général une meilleure qualité de code.
    Par exemple dans les langages l’architecture SOLID est très liée à la testabilité d’un code.

NOTE: On va entrer dans le vif du sujet. Je ne vais volontairement pas parler de TDD, ce n’est pas le sujet de l’article. Mais je vous recommande de vous y intéresser.

Installer Pester

Pester est un module PowerShell, l’installation est très simple:

Import-Module Pester -Passthru

C’est tout. Une autre ressource utile, la doc:

https://pester.dev/docs/quick-start

Le pattern AAA

Il y a des années e cela, j’ai travaillé sur un projet assez gros. Avec l’équipe on décide de faire des tests unitaires. Dans le monde .NET c’était assez nouveau à l’époque et on avait envie de se faire la main et apprendre.

On a écrit des milliers de tests, vraiment.

Puis est venu le temps des refactorisation, d’évolution des spécifications, de correction de bugs…

On a modifié le code, on s’attendait à ce que parfois certains tests plantent car la logique avait changé. On ne s’attendait pas à en voir parfois des dizaines s’effondrer pour des modifications mineures. Et encore moins de voir planter des tests qui en apparence n’avaient rien à voir.

Que s’est-il passé?

Il s’est passé que l’on n’avait pas compris que les tests c’est aussi du code et doit donc être écrit avec de bonnes méthodes. Pour se faciliter la vie la plupart de nos tests testaient des tas de trucs complètement différents. On n’allait tout de même pas réécrire un test spécifique alors qu’une seule ligne change? N’est-ce pas?

Pire ces tests étaient même des usines avec des ifs dans tous les sens. Bref on n’avait jamais entendu parler du pattern AAA.

AAA signifie:

  • Arrange
    On créée sont contexte de test: initialisation de variable, construction de certaines ressources…
  • Act
    On réalise le test. En général on appelle une fonction en lui passant les paramètres précédents
  • Assert
    On vérifie que la condition testée

En lisant ceci, on dirait une blague. c’est ça un pattern?

Ce qui compte dans un pattern c’est son intention, c’est à dire le problème qu’il essaye de résoudre. La façon dont on le formule que ce soit du texte comme ici, un bout de code, un diagramme UML… n’est le plus souvent pas le meilleur angle pour en montrer l’intérêt.

Par exemple Proxy et Adaptator sont deux patterns différents, mais ils ont (quasi) le même diagramme UML. Oui, mais ils résolvent deux problèmes différents. J’ai écrit un article là-dessus il y a fort longtemps:

https://amethyste16.wordpress.com/2014/07/02/comparaison-des-patrons-proxy-adaptateur-et-facade/

Alors quelle est l’intention de AAA?

Un test doit tester un scénario et un seul

On aurait suivi ce pattern on aurait écrit des scripts bien plus simples et parfaitement indépendants.

Donc voilà, un script de tests doit être simple, il ne faut pas avoir peur d’en écrire beaucoup.

Premier contact

Structure d’un test

Regardons cet exemple extrait de la doc:

function Get-Planet () {
    $planets = @(
        @{ Name = 'Mercury' }
        @{ Name = 'Venus' }
        @{ Name = 'Earth' }
        @{ Name = 'Mars' }
        @{ Name = 'Jupiter' }
        @{ Name = 'Saturn' }
        @{ Name = 'Uranus' }
        @{ Name = 'Neptune' }
    ) | ForEach-Object { [PSCustomObject] $_ }

    $planets 
}

Describe 'Get-Planet' {
    It 'Lists all 8 planets' {
        $allPlanets = Get-Planet
        $allPlanets.Count | Should -Be 8
    }
}

NOTE: je l’ai un peu modifié pour qu’il marche. Le code qui marche ou ne compile pas est une constante dans la doc Pester.

C’est un tuto, je ne vais pas aller dans les détails et plutôt vous renvoyer à la doc. Mais en gros:

Le bloc:

Describe ‘Get-Planet’

Contient les tests unitaires. Un test unitaire fait partie du bloc It. It contient un et un seul test (AAA souvenez vous).

Il n’y a pas à proprement parler d’étape Arrange

NOTE: Dans la vie réelle, cela n’arrive jamais de définir la fonction à tester directement dans le script. La fonction ferait partie d’un module PowerShell que l’on importe.
C’est une mauvaise pratique de mélanger code et test.

On appelle la méthode Get-Planet. C’est l’étape Act. On effectue une action et on veut tester son résultat.

Vient ensuite l’étape Assert. Le cœur de cette étape est la commande Should. On teste que l’appel à la fonction retourne 8 noms de planètes.

Lancer les tests

Pester c’est du Powershell, on peut dans le lancer directement:

2022-08-30_23-26-22

Quand les tests sont nombreux, ce n’est pas pratique. Alors Pester est accompagné d’une cmdlet magique:

Invoke-Pester

Cette commande parcourt l’ensemble de votre projet, recherche les tests et les lance. Par défaut il recherche les fichiers en *.Tests.ps1, mais c’est configurable.

Sa documentation se trouve ici:

https://pester.dev/docs/commands/Invoke-Pester

Et ainsi:

2022-08-30_23-28-13

On découvre un peu plus de détails. On apprend que 1 test à réussi et 0 ont échoués.

Architecture générale des tests Pester

Un test est toujours présent à l’intérieur d’un bloc.

Le plus souvent ce sera Context ou Describe. Ces blocs peuvent s’emboîter les uns les autres. Ils servent à créer des contextes que l’on peut emboîter et n’ont pas d’autres actions sur l’exécution du test.

Supposons par exemple un module spécialisé dans des opérations sur des ressources Azure. On peut imaginer que les tests qui concernent les Azure App Service (ASE) soient rassemblés dans le même bloc:

Describe 'Azure Web Apps' {

}

On peut avoir des tests génériques et des tests spécifiques aux services plans, aux ASE… Dans ce cas on peut structurer le bloc ainsi:

Describe 'Azure Web Apps' {
    context "ASE" {
        it "should have a valid ASE" {
   
        }
    }

    contexte "Service Plan" {
        it "should have a valid SPN" {

        }
    }
}

Block Describe

Le bloc Describe organise un groupe de tests.

  • Describe définit une portée (scope)
  • Describe contient un nombre arbitraire de blocs Context ou It
  • Les Mocks définis dans Describe sont nettoyés à la fin du bloc
  • On peut ajouter des blocs BeforeAll ou BeforeDiscovery. dans lesquels on peut définir des variables dont la portée est Describe

Block Context

Un bloc Context fait partie d’un bloc Describe. Il dispose des propriétés suivantes:

  • Context définit une portée (scope) dans un bloc Describe
  • Un bloc Context peut contenir des blocks BeforeAll ou BeforeDiscovery. dans lesquels on peut définir des variables dont la portée est Context
  • La portée de ces blocs BeforeAll ou BeforeDiscovery est Context 
  • Chaque Mock défini dans un Context est nettoyé à la fin
  • Context peut contenir un nombre arbitraire de bloc It

Note: Les blocs BeforeAll et BeforeDiscovery ont déjà fait l’objet d’un article :

https://amethyste16.wordpress.com/2022/08/20/beforediscovery-ou-beforeall/

Block It

It défini un test unitaire. Il est obligatoirement dans un bloc Describe ou Context.

Le corps de It doit idéalement répondre au modèle AAA.

Laisser un commentaire