Skip to main content

Devops – Unit testing in Powershell Part 2

image

Powershell has really gained a strong momentum the last 3-4 years and is becoming increasingly the scripting language of choice if you live in the Windows part of the IT world. Together with the increasing popularity of the Devops phrase, unit testing is a key factor going forward.


This is part 2 (Part1) of a small series of posts related to unit testing in Powershell. You can read the first post her.

In the previous post we presented the Scenario we will be working with and the API functions that our SillyModule will wrap logic around.

The tasklist
  1. Read up on Unit Testing frameworks for powershell (Pester or PSUnit)
  2. Create the the SillySystem Import-Object and Export-Object API
  3. Walk through of the module and the functions (Get/Update-Person/Group)
  4. Create tests for the functions
  5. Refactor where needed ad update the tests
The SillyModule

The context of the module is pretty simple. We need cmdlets to receive a list of persons or groups. The list may be filtered on a specific property (Name or ObjectID). Next we need cmdlets to update/modify the properties of a person or group. Currently our module does not support adding or deleting objects.

Functions exported from the module:

image

To additional helper functions added that we have not talked about.
  • Get-Object – Is a wrapper for the Export-Object command/API. All cmdlets with verb Get calls this where the xPath is built for Export-Object.
  • Remove-HashtableNullValue – Is a helper function that removes keys from an hashtable that contains $null values. It is used in the Get-Object function.

To better understand how the cmdlets works, I have created a very simple flow chart for Get-Person and Update-Person.

image
image

The SillyModule and SillySystem are both available on GitHub:
Clone them or download them from GitHub to play around and see how they work. Run the build.ps1 script to build the module.

Our first test

Please bear in mind that I am not an expert on unit testing and I urge anyone with experience and knowhow to guide us if I make the wrong turn. If you have not done it yet, clone or download the Pester module from GitHub.

I usually start in the middle of the call stack, that is as close as possible to the functionality that is not under my control. In our scenario that would imply that we should start with Get-Object function.

The Pester module comes with a function to help you get started with testing. It is called New-Fixture. It will generate 2 files:
  • One file for the function you want to develop (Test Driven Development remember)
  • One file that will hold the tests for the function. This scriptfile will contain some startup magic to make pester run its test and a dummy test for the function.
What happens if we run this:

image

New-Fixture will output an warning and inform us that it found a file called Get-Object.ps1 and skip the creation of it, but the test file will be created and called Get-Object.Tests.ps1:

image

Sweet we have our test file. Lets crank it open in ISE and have a look:

psedit .\functions\Get-Object.Tests.ps1

image

Turns out a Pester unit test is just a script file with some magic and keywords that describe (duh) the unit tests. Notice the red arrow and the . (dot). Pester dotsource the Get-Object.ps1 file which contains our function Get-Object into the script scope of the test. If you don’t know what dotsourcing is, look it up it is out there.

There are some rules. In my testing I use three keywords: Describe, Context and It. 

  1. A test file can have multiple Describe scriptblocks. 
  2. A Describe can have muliple Context blocks and It blocks
  3. Context- and It blocks must be contained within a Describe scriptblock

Now to the fun part. The It block contains the actual testing. In our current test is says that $true should be equal to $false. No need to say that this test will never pass in it’s current form. To run a test-file, we use the Invoke-Pester cmdlet and give it a test file. Lets run it and confirm:


image


It says it expected the value to be False, but it was True. Change the test to $true | Should be $true, save the testfile and run it with Invoke-Pester:


image


There it passed the test, which should come as no surprise to you. It is a silly test for a silly module! Time for some more realistic testing for our Get-Object function in the next part.

Cheers

Tore

Comments

  1. Hello,
    The Article on Unit testing in Powershell is very informative. It give detail information about it .Thanks for Sharing the information on Unit Testing in Power Shell. Software Testing Company

    ReplyDelete

Post a Comment

Popular posts from this blog

Serialize data with PowerShell

Currently I am working on a big new module. In this module, I need to persist data to disk and reprocess them at some point even if the module/PowerShell session was closed. I needed to serialize objects and save them to disk. It needed to be very efficient to be able to support a high volume of objects. Hence I decided to turn this serializer into a module called HashData. Other Serializing methods In PowerShell we have several possibilities to serialize objects. There are two cmdlets you can use which are built in: Export-CliXml ConvertTo-JSON Both are excellent options if you do not care about the size of the file. In my case I needed something lean and mean in terms of the size on disk for the serialized object. Lets do some tests to compare the different types: (Hashdata.Object.ps1) You might be curious why I do not use the Export-CliXML cmdlet and just use the [System.Management.Automation.PSSerializer]::Serialize static method. The static method will generate t...

Toying with audio in powershell

Controlling mute/unmute and the volume on you computer with powershell. Add-Type -TypeDefinition @' using System.Runtime.InteropServices; [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IAudioEndpointVolume { // f(), g(), ... are unused COM method slots. Define these if you care int f(); int g(); int h(); int i(); int SetMasterVolumeLevelScalar(float fLevel, System.Guid pguidEventContext); int j(); int GetMasterVolumeLevelScalar(out float pfLevel); int k(); int l(); int m(); int n(); int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, System.Guid pguidEventContext); int GetMute(out bool pbMute); } [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] interface IMMDevice { int Activate(ref System.Guid id, int clsCtx, int activationParams, out IAudioEndpointVolume aev); } [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), Inte...

Creating Menus in Powershell

I have created another Powershell module. This time it is about Console Menus you can use to ease the usage for members of your oranization. It is available on GitHub and published to the PowershellGallery . It is called cliMenu. Puppies This is a Controller module. It uses Write-Host to create a Menu in the console. Some of you may recall that using Write-Host is bad practice. Controller scripts and modules are the exception to this rule. In addition with WMF5 Write-Host writes to the Information stream in Powershell, so it really does not matter anymore. Design goal I have seen to many crappy menus that is a mixture of controller script and business logic. It is in essence a wild west out there, hence my ultimate goal is to create something that makes it as easy as possible to create a menu and change the way it looks. Make it easy to build Menus and change them Make it as "declarative" as possible Menus The module supports multiple Men...