
When I had to write complex, large PowerShell scripts and change them over time, I wanted to find a tool that would simplify the verification of the performance of my scripts. Such a tool turned out to be Pester, a unit testing framework.
That he can and the basics of its use, and I will tell.
Pester allows you to write tests to any commands or scripts executed in powershell. Including functions, cmdlets, modules. It allows you to group tests, so that you can run all the tests at once, or only the tests of a specific function or the tests of all the functions of a specific script.
Pester can be launched in the powershell console or integrated into the development environment. At the same time, it can be either one of the development environments created for PowerShell (PowerShell ISE, etc.), or Visual Studio using
PowerShell Tools for Visual Studio 2015 . Pester will help you if you have heard about
development through testing and would like to try it for developing your scripts. And if you have ready-made scripts for which you want to do tests - Pester will also help you.
')
How to start. Download and integrate Pester with PowerShell ISE
Pester is a powershell module written by Scott Muc and
published on Github. In order to use Pester you just need to download it and unpack it into a folder in one of the Modules folders on your computer.

What is the Modules folder?Folders Modules several. For example, the Modules folder along the% UserProfile% \ Documents \ WindowsPowerShell \ Modules path allows you to store modules that are only needed by your account. And, as a rule, this folder does not exist until you create it yourself. And in the folder% windir% \ system32 \ WindowsPowerShell \ v1.0 \ Modules are stored modules that are available to all users.
The current list of folders for storage of powershell modules for your system is stored in the $ env: PSModulePath environment variable. For example, the list of Modules folders from my computer:
PS C:\> $env:PSModulePath -split ';' F:\Users\sgerasimov\Documents\WindowsPowerShell\Modules C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
This list may change when installing software, for example, the Lync Server administration tools add to the list the path to the folder with its modules.
Use the Modules folder in the current user profile. Create it using Explorer or using powershell, as shown below:
cd $env:USERPROFILE\documents new-item -Name WindowsPowerShell -ItemType directory new-item -Path .\WindowsPowerShell -Name Modules -ItemType directory

After this, unzip the archive to the Pester folder in the Modules folder.

To integrate Pester with PowerShell ISE, create a file Microsoft.PowerShellISE_profile.ps1 in the% UserProfile% \ Documents \ WindowsPowerShell folder with the following content:
try { Import-Module Pester } catch { Write-Warning " Pester " }
If the file already exists, simply add the above code to the file.
Now, every time you start the PowerShell ISE module, Pester will load automatically and you just have to use it.
How to write tests and execute? General scheme
Tests are written in separate files. By default, the following solution is proposed:
For each script, a file is created with the name of the script name.Tests.ps1. For example, you have the CreateUser.ps1 script or you plan to write a script with that name. Then you put the tests for this script and its functions in the CreateUser.Tests.ps1 file.
When you write tests and run them, Pester will view all files with ".Tests." In the name in the current and in the subdirectories and run tests from them. This allows, for example, to store test files in a subfolder, and not in a folder with scripts.
The test file is a powershell script with groups of tests. You can specify multiple levels of nesting of test groups using the Describe and Context commands. The It command describes 1 test.
Let me give you a very simple example that will show us how to use Pester to write and run tests. To understand the scheme.
Example
Suppose you have a script that returns after performing “Hello World!” And you need to write a test for it.
You already have the HelloWorld.ps1 script file:
return "Hello world!"
Create a file called HelloWorld.Tests.ps1. It will contain a test for your script, which will check that the script returns “Hello world!” After launch:
Describe " HelloWorld" { it " Hello World!" { $result = .\HelloWorld.ps1 $result | Should Be "Hello World!" } }
The Describe block describes in general which script is being tested, and the It block contains the test itself. First line
$result = .\HelloWorld.ps1
executes the script and its results, and then the string
$result | Should Be "Hello World!"
describes what should be the result. To do this, use the command
Should which checks the compliance of the obtained value with the specified condition. And the condition is set by the
Be operator, who says that the condition is an equality to the string “Hello World!”.
If the test specified by the
Should command succeeds, then the test is passed, otherwise the test is considered failed.
Copy the code above into the HelloWorld.Tests.ps1 file and save this file.
After that, make sure that the current directory points to the folder where the HelloWorld.ps1 and HelloWorld.Tests.ps1 files are located. I have this “F: \ Projects \ iLearnPester \ Examples>” and run the
Invoke-Pester command to run the tests:

The test was successful. This is indicated by the green color of the line with the name of the test (corresponds to the phrase after the block It). If the test fails, the test name is displayed in red, and the following indicates what went wrong.

The expected string was “Hello World!”, But the script returned the string “Hello all!”. In addition, the test file and the line on which the failed test is in the test file are indicated.
If you want to try development through testing . Then you first write the test, and then the script / function to it.
The
Should command and the statement following it (for example,
Be ) together create an Assertion. Pester has the following statements:
- Should be
- Should beExactly
- Should exist
- Should Contain
- Should ContainExactly
- Should match
- Should MatchExactly
- Should throw
- Should BeNullOrEmpty
Inside statement you can always insert Not and make a negative, for example: Should Not Be, Should Not Exist.
I'll tell you more about the approvalShould be
Compares one object to another and throws an exception if the objects are not equal. Strings are ignored case insensitive, numbers, arrays of numbers and strings. User objects (pscustomobject) and associative arrays are not compared.
# $a = "" $a | Should Be "" # $a | Should Be "" # $a | Should Be " " # $a | Should Not Be " " # # $a = 10 $a | Should Be 10 # $a | Should Be 2 # $a | Should Not 2 # # $a = 1,2,3 $a | Should Be 1,2,3 # $a | Should Be 1,2,3,4 # $a | Should Be 4,5,6 # # $a = "qwer","asdf","zxcv" $a | Should Be "qwer","asdf","ZXCV" # $a | Should Be "qwer","asdf","zxcv", "rrr" #
Should beExtactly
Same as Should Be, only strings are compared case sensitive.
$actual="Actual value" $actual | Should BeExactly "Actual value" # $actual | Should BeExactly "actual value" #
Should exist
Checks that the object exists and is available to one of the PS providers. The most typical is to check that the file exists. Essentially executes the test-path cmdlet for the passed value.
$actual=(Dir . )[0].FullName Remove-Item $actual $actual | Should Exist # import-module ActiveDirectory $ADObjectFQDN = "AD:CN=Some User,OU=Users,DC=company,DC=com" $ADObjectFQDN | Should Exist # $registryKey = "HKCU:\Software\Microsoft\Driver Signing" $registryKey | Should Exist # .
Note that you can only check for the presence of a registry branch in this way, but not for a specific key, since PS provider working with the registry gives access to the keys as properties of registry branches. He does not consider them objects.
Should Contain
Checks that the file contains the specified text. The search is case insensitive and can use regular expressions.
Set-Content -Path c:\temp\file.txt -Value ' ' 'c:\temp\file.txt' | Should Contain ' ' # 'c:\temp\file.txt' | Should Contain '*' #
Should ContainExactly
Checks that the file contains the specified text. The search is case sensitive and can use regular expressions.
Set-Content -Path c:\temp\file.txt -Value ' ' 'c:\temp\file.txt' | Should Contain ' ' # 'c:\temp\file.txt' | Should Contain '*' #
Should match
Compares two strings using case-insensitive regular expressions.
"" | Should Match "." # "" | Should Match ([regex]::Escape(".")) #
Should MatchExactly
Compares two strings using case-sensitive regular expressions.
"" | Should Match "" # "" | Should Match "." #
Should throw
It is considered true if an exception occurs in the script block being tested. You can also specify the expected exception text.
A script block is passed to the input. With functions, unfortunately, does not work.
{ } | Should Throw # { throw " " } | Should Throw " " # { throw " " } | Should Throw " " # {throw " "} | Should Throw "" # { $foo = 1 } | Should Not Throw #
Should BeNullOrEmpty
Checks that the value passed is $ null or empty (for a string, array, etc.). It is worth recalling that $ null is not 0.
$a = $null $b = 0 $c = [string]"" $d = @() $a | Should BeNullOrEmpty # $b | Should BeNullOrEmpty # $c | Should BeNullOrEmpty # $d | Should BeNullOrEmpty #
What else can he do?
Mock functions.
In Pester, there are mock functions that allow you to override any function or cmdlet before calling a test.
For example, you are developing a script that will receive the ip-address of the current machine and, depending on which network this address belongs to, prescribe this or that dns-server in the adapter settings. But your machine, on which you are developing a script, has only 1 ip address and it is troublesome to change it for tests. Then you just before calling the test redefine the function that receives the ip-address so that it returns not the current address, but the one you need to check.
Here is a sketch of our script (let's call it SmartChangeDNS.ps1).
$MoskowNetworkMask = "192.168.1.0/24" $RostovNetworkMask = "192.168.2.0/24" $IPv4Addresses = GetIPv4Addresses foreach($Address in $IPv4Addresses) { if(CheckSubnet -cidr $MoskowNetworkMask -ip $Address) { # dns 192.168.1.1 } if(CheckSubnet -cidr $RostovNetworkMask -ip $Address) { # dns 192.168.2.1 } }
He knows 2 network masks in Moscow and Rostov. Receives, using the
GetIPv4Addresses function
, all IPv4 addresses of the current machine and then, in the
foreach loop, checks whether a subnet address is a
member of the CheckSubnet function. The functions
GetIPv4Addresses and
CheckSubnet you have already written and checked. Now, to check the functions as a whole, we need to write tests in which we override the function
GetIPv4Addresses so that it returns the desired address. Here is how it is done:
describe "SmartChangeDNS" { it " 192.168.1.0/24" { Mock GetIPv4Addresses {return "192.168.1.115"} .\SmartChangeDNS.ps1 $DNSServerAddres = Get-DnsClientServerAddress -InterfaceAlias "Ethernet" -AddressFamily IPv4 | Select -ExpandProperty ServerAddresses $DNSServerAddres | Should Be "192.168.1.1" } it " 192.168.2.0/24" { Mock GetIPv4Addresses {return "192.168.2.20"} .\SmartChangeDNS.ps1 $DNSServerAddres = Get-DnsClientServerAddress -InterfaceAlias "Ethernet" -AddressFamily IPv4 | Select -ExpandProperty ServerAddresses $DNSServerAddres | Should Be "192.168.2.1" } }
Now, when the script is executed, it will come to the execution of the
GetIPv4Addresses function, it will be executed not in the version indicated in the script, but in the one we defined with the Mock command.
Redefining functions with Mock allows you to abstract, when needed, from external systems, modules, or called functions.
Testdrive
Pester also provides a temporary PS disk that can be used to work with the file system as part of test execution. Such a disk exists within a single Describe or Context block.
If the disc is created in the Describe block, then it and all files created on it are visible and available for modification in Context blocks. Files created in the Context block with the completion of this block are deleted and only files created in the Describe block remain.