- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
Accurate integration tests require starting from a clean slate. It's important not to reuse these dependencies over and over, as this can eventually lead to inaccurate tests. Run enough tests on a single VM, and eventually your well-meaning code that's not supposed to change anything eventually will. You might forget to clean up changes, revert the snapshot, and so on. However, although it's important to start as cleanly as possible, you will find it extremely time-consuming to do so. After all, who wants to worry about bringing up an entirely new VM just to test a software install or a Windows feature?
I've found a happy medium for the majority of the infrastructure dependencies I have to work around when building accurate integration tests for my PowerShell code. Let's break these concepts down and see how you can manage these dependencies without going crazy in the process.
Pre-Testing
In a perfect world, before every integration test, you'd create the entire environment from scratch in a pristine state. Most of the time, this isn't possible. In such cases it's important that you confirm the state of your environment before running the test. This way, you can be sure that your code actually made the changes you thought it did.
To demonstrate this, let's assume you have a PowerShell script called NewVMScript.ps1 that creates a Hyper-V VM with a particular name. If you're not worrying about dependencies, your test might look like this:
describe 'New VM test' { it 'creates a new VM' { ## Execute the script to create the VM .\NewVMScript.ps1 –Name MYVM ## Check to see if the VM was created Get-VM –Name MYVM | should not be $null } }
This will work well if you don't already have a VM called MYVM before running your script. It's important to first validate the environment before running your script to ensure everything is in the state you expect. A better version of this test might look something like this:
describe 'New VM test' { it 'creates a new VM' { if (Get-VM –Name MYVM) { Write-Error –Message "The test VM name MYVM already exists." } else { ## Execute the script to create the VM .\NewVMScript.ps1 –Name MYVM ## Check to see if the VM was created Get-VM –Name MYVM | should not be $null } } }
This way, the test won't even attempt to run.
The Pester Set-TestInconclusive command
Taking pre-testing a bit further, let's use Pester's built-in Set-TestInconslusive command. This is a command that you can use to neither test nor fail a failure. Instead, Pester will just throw its metaphorical hands up and tell you it doesn't know what's going on. This is a great command to use in pre-testing because if a dependency isn't there before the test runs, the test cannot pass or fail because it won't run in the first place. Set-TestInconclusive is a perfect command to run when a dependency does not exist.
Let's review our previous example, but this time, instead of throwing an exception, I'll use Set-TestInconclusive instead.
describe 'New VM test' { it 'creates a new VM' { if (Get-VM –Name MYVM) { Set-TestInconclusive –Message "The VM already exists." } else { ## Execute the script to create the VM .\NewVMScript.ps1 –Name MYVM ## Check to see if the VM was created Get-VM –Name MYVM | should not be $null } } }
Instead of failing, the command returned a question mark
You can now see that instead of failing, the command returned a question mark. This is more accurate than a failure, as my script didn't even get a chance to run.
The Before, After, BeforeAll, and AfterAll Pester constructs
Finally, perhaps you need to perform some pre-checking of dependencies or would like to set up some kind of test scenario ahead of time. You have some code that you'd like to run, like creating a new VM or identifying some specific settings your script is supposed to change. This code needs to run before your test runs, and you might need to clean up these changes after your tests run. This is where the Before* and After* Pester constructs come into play.
These constructs allow you to run some code before and after each Pester It block or before or after all of the It blocks are processed. This is perfect for setting up some things and tearing them down afterward.
Let's take our previous example of creating a VM a bit farther this time. Perhaps you have some code that builds a web server. To ensure a clean environment before the test runs, you always need to create a brand-new VM and install the Web-Server Windows feature. After you run the test, you need to throw the VM away. Doing this requires the use of the BeforeAll and AfterAll constructs.
To use these constructs, just place them inside of the describe block.
describe 'new web server' { BeforeAll { ## New-VM –Name SOMEVM ## New-Website –ComputerName SOMEVM –Name whatever } AfterAll { ## Tear down the VM that was created for testing Remove-VM –Name SOMEVM } ## Execute the script to create the web server .\NewWebServer.ps1 it 'creates the VM' { ## Ensure the VM was created Get-VM –Name SOMEVM | should not be $null } it 'sets some IIS settings' { ## Check to see if the settings were done correctly Get-Website –Name whatever –ComputerName SOMEVM | should not be $null ## other assertions here } }
This test block will first create a fictional VM called SOMEVM and build a new website on it called "whatever." Pester will then test to ensure the VM was created and then if the website was set up correctly. When complete, it will then tear down the VM. This guarantees a pristine test environment and that you don't leave anything behind.
Also, notice how I was able to remove the Set-TestInconclusive cmdlet? This is because I'm building my perfect scenario ahead of time. There's no need to check to see if it's in the right state before running the test.
Building integration tests in Pester can be challenging, partly because Pester wasn't designed for this. However, by using some creative code and being patient as you wait for the tests to run, you can ensure you're testing the attributes your script is changing rather than fighting with pre-existing environmental settings.
Subscribe to 4sysops newsletter!
For a full breakdown on what's possible with the PowerShell testing framework Pester check out The Pester Book by myself and Don Jones.
The code to actually make the changes was fictional. I was trying to teach a concept of how to manage the dependencies; not actually do any work. However, I don’t see any code other than what I intended that was commented out.