Version: 5.1.x
This post will be fully dedicated to how you can automate tests for your PowerShell scripts. As a scenario, you have written some new code or modified some old code and now you want to test these changes, in an automated way, before putting them in the Production environment.
Introducing Pester
Pester is one testing and mocking framework for PowerShell that’ll provide you with a framework for writing and running tests. Its a open source project that’s bundled with Windows 10 and later.
What we’ll test?
Since we are introducing this automated testing concept and we need a script to test, why not use something already created on this blog!? Therefore, we’ll test the function created during the post “Building a function | Powershell” (If you didn’t read it, now its a good time for it, otherwise will be hard to understand the reason of our tests).
As the first step, we need to load the Pester module and create a new text fixture to allow us test our scripts. For that we will use the following cmdlets.
Import-Module Pester
Mkdir testOurServiceFunction
New-Fixture -Path testOurServiceFunction -Name Set-ServiceRunningStatus
Notice, on the above image, you should end with two PowerShell script files… One for your code and the other where you’ll built your testing conditions.
Let’s focus mainly on the Set-ServiceRunningStatus.Tests.ps1. Here, if you open your file, you should have something similar to the following…
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here\$sut"
Describe "Set-ServiceRunningStatus" {
It "does something useful" {
$true | Should Be $false
}
}
Explaining a bit of what you have on your test script… The first three commands are to link the test code with your code script. The next two sections are:
- Describe – This block is designed to contain a set of tests. Since we’re introducing this automated test concept we’ll only stick with this one, but more complex are your tests, more often will be to have multiple Describe blocks;
- It – This block represents a single test where your code will either pass or fail. A Describe block often contains many It blocks.
How we’ll test?
First things first, we’ll start by mocking the principal “actions” from our script and are they, Restart and Start services:
Mock Restart-Service { return 1 }
Mock Start-Service { return 1 }
With this, we will be able to get a counter of actions triggered and we can compare with what we are testing for the moment of our It clause.
Let’s start with a basic test… Our goal will be “accepts and restarts one running service” and the way we will achieve it will be by using the following:
It "accepts and restarts one running service" {
$result = 0
Get-Service -Name '*_W1*' | Where-Object { if ($_.Status -eq 'Running') { $result += 1 } }
Set-ServiceRunningStatus -Name '*_W1*'
Assert-MockCalled Restart-Service -Exactly $result -Scope It
}
On the above test, we are looking for a restart on running services with Name that includes _W1 string and counting the ones that will be impacted. After that, we will invoke our function and check with the Assert-MockCalled if the number of restart are coincident with the number of Running Services with name that includes _W1.
Let’s execute and see the results so far…
Well, so far so good, 1 out of 1 passed. But how much, quantitively, I’ve covered in my script with the last test!?
Let’s see by using the parameter -CodeCoverage.
invoke-pester -CodeCoverage .\Set-ServiceRunningStatus.ps1
We have half of the script, tested. As you can see, besides having the covered percentage we have also the missed commands so far. Let’s give it a try to add more tests and try to cover more amount of the script.
It "accepts and restarts multiple running services" {
$result = 0
Get-Service -Name '*Pharma*' | Where-Object { if ($_.Status -eq 'Running') { $result += 1 } }
Set-ServiceRunningStatus -Name '*Pharma*'
Assert-MockCalled Restart-Service -Exactly $result -Scope It
}
It "accepts and restarts one running service" {
$result = 0
Get-Service -Name '*_W1*' | Where-Object { if ($_.Status -eq 'Running') { $result += 1 } }
Set-ServiceRunningStatus -Name '*_W1*' -Force
Assert-MockCalled Restart-Service -Exactly $result -Scope It
}
It "accepts and restarts one running service on a Computer" {
$result = 0
Get-Service -ComputerName 'PTPOPF247203' -Name '*_W1*' | Where-Object { if ($_.Status -eq 'Running') { $result += 1 } }
Set-ServiceRunningStatus -ComputerName 'PTPOPF247203' -Name '*_W1*'
Assert-MockCalled Restart-Service -Exactly $result -Scope It
}
With a few more, like the above ones, we have been able to achieve the following coverage:
And that’s it. Lightly we have been able to see how to test our PowerShell scripts and automate this annoying task… At least for me, it is an annoying one.
We have also been able to see a metric of the code covered by our tests and have a guide of how to achieve better tests and inherently, better solutions with this testing framework.
Note: Besides internet references, I’ve also used the learning concepts acquired in the following book. I advise it as good reading.
Stay safe!
Top comments (0)