PowerShell How-To

How I Test My PowerShell Code

As your PowerShell scripts become more and more important to the organization, simply eyeballing the results a few times and confirming the script doesn't return an error isn't a good enough test. Especially, if the PowerShell code youwrite is maintaining production systems, it's critical that some extra steps are taken to run that script through many different scenarios to ensure it's rock-solid. The way to do this is by creating Pester tests.

Pester is an open source unit testing framework. Pester is used to write tests for PowerShell code to confirm that what it does is what you expect. In a nutshell, Pester is the code that's written "on top of" your code to act as quality assurance to ensure your code is working as it should. Fortunately, Pester itself is written in PowerShell, so you don't have to be a software developer to learn how to use it. As long as you can learn a few syntactical differences between vanilla PowerShell and Pester, you'll be OK.

Since Pester is just a PowerShell module, we can easily download and install it from the PowerShell Gallery. This will get you the latest version. However if for some reason, you don't want to get the most recent version, Pester comes installed by default on Windows 10.

Install-Module -Name Pester

Once you've got the Pester module installed, it's now time to start creating some tests. Pester tests are just PowerShell scripts written a specific way, but it's recommended that all test scripts end with .Tests.ps1. This isn't technically required but does come in handy later when invoking lots of test scripts at once.

Pester test scripts are broken down into a hierarchy of three distinct "levels." These levels are describe, context and it. All are mandatory except for the context block.

describe 'Describe level' {
  context  'context level' }
it 'it level' {

}
}
}

The highest "level," the describe block is a container for multiple it blocks. The describe block can be created some different ways, but one common method is to create a describe block per function. Then, inside of the describe block to include multiple it blocks which will be your actual tests or assertions. To keep things simple, let's create a couple of tests for a single function. This function accepts a single parameter ComputerName and then returns either $true if the computer is online or $false if the computer is offline.

function Ping-Computer {
param($ComputerName)

Test-Connection -ComputerName $ComputerName
}

Granted, this isn't the most creative example function because you'd just call Test-Connection by itself but will still demonstrate this simple situation.

What does this function "do"? This may seem obvious, but it's important to ask the question to yourself and slowly walk through the scenarios in which it might be executed.

  1. What happens when $ComputerName is FOO and FOO isn't even a real computer?
  2. What happens when $ComputerName is BAR and BAR is a real computer but can't be resolved via DNS?
  3. What happens when $ComputerName is a real computer but does not respond to ping?
  4. What happens when $ComputerName is a real computer but does respond to ping?

These are the kinds of questions you must ask yourself to begin to build real tests. For now, we're just going to assume that ComputerName is a real computer and can be resolved via DNS. Since we're making this assumption, we then only have to create two tests; one test to ensure $true is returned and one test to ensure $false is returned given the particular scenario of Test-Connection returning $true or $false. But, how do we make Test-Connection return $true or $false. We're not going to do some DNS trickery or something environmental. Instead, we can mock Test-Connection to force it to return whatever we want.

Let's create a simple describe block and two it blocks representing our scenario.

describe 'Ping-Computer' {

it 'should return $true when the computer is online' {

}

it 'should return $false when the computer is offline' {

}
}

We now need to figure out how to simulate "the computer is online" and "the computer is offline." To do this, we'll create a mock for Test-Connection.

mock 'Test-Connection' -MockWith {  $true }

Mocks, in Pester, are ways to force commands to return what we need them to given the situation. In this case, we need Test-Connection to return $true or $false at will. Above, you can see that I'm forcing Test-Connection to return $true. I will go ahead and insert this into my test as well as the opposite $false situation.

describe 'Ping-Computer' {

it 'should return $true when the computer is online' {
mock 'Test-Connection' -MockWith { $true }

Ping-Computer -ComputerName 'DOESNOTMATTER'
}

it 'should return $false when the computer is offline' {
mock 'Test-Connection' -MockWith { $false }

Ping-Computer -ComputerName 'DOESNOTMATTER'
}
}

Also, notice that we're running the function inside of each it block. Pester is, in a sense, "recording" that execution and capturing how it's running. Once we manipulate how the function is run, we can now use the should keyword to compare the actual output of the function to what we expect it to be.

describe 'Ping-Computer' {

it 'should return $true when the computer is online' {
mock 'Test-Connection' -MockWith { $true }

Ping-Computer -ComputerName 'DOESNOTMATTER' | should be $true
}

it 'should return $false when the computer is offline' {
mock 'Test-Connection' -MockWith { $false }

Ping-Computer -ComputerName 'DOESNOTMATTER' | should be $false
}
}

This test can now be ran using the Invoke-Pester command inside of the Pester module. You can see that it returns two successes.

PS> Invoke-Pester -Path  C:\Ping-Computer.Tests.ps1

Describing Ping-Computer
[+] should return $true when the computer is online 285ms
[+] should return $false when the computer is offline 144ms

We barely scratched the surface of how to write tests for PowerShell. For a full tutorial including tons of example and best practices, I encourage you to check out The Pester Book, written by me.. I go over all of the syntaxes and go into tons of scenarios using best practices to ensure you go from knowing nothing about Pester to being a pro.

About the Author

Adam Bertram is a 20-year veteran of IT. He's an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and content marketing advisor to multiple technology companies. Adam also founded the popular TechSnips e-learning platform. He mainly focuses on DevOps, system management and automation technologies, as well as various cloud platforms mostly in the Microsoft space. He is a Microsoft Cloud and Datacenter Management MVP who absorbs knowledge from the IT field and explains it in an easy-to-understand fashion. Catch up on Adam's articles at adamtheautomator.com, connect on LinkedIn or follow him on Twitter at @adbertram or the TechSnips Twitter account @techsnips_io.


comments powered by Disqus
Most   Popular