Introduction
I am glad to announce that the first official release of PSUnit is now available for download on Codeplex http://www.psunit.org/Release/ProjectReleases.aspx. This release is a fully functional unit testing framework that lets you write and execute unit tests in PowerShell. It also provides a nice test result reporting feature. The Beta release is missing some convenience features, like an automatic deployment of an PowerShell module. Instead there are a few manual steps required to deploy the framework successfully. To increase the usability in the near future I am also extending the PSUnit integration with the PowerShell ISE.
Note: This release of PSUnit requires PowerShell 2.0 CTP3 or later! Click here to download this version of PowerShell
Writing PSUnit tests
The development of unit tests is relatively straight forward and very similar to the concepts of NUnit for example. The sample code that this post uses can be found in the PSUnit\Samples folder that is part of the PSUnit download. The files are called Interpolate-Records.ps1 (script under test) and Interpolate-Records-Test.ps1 (test script).
Features that are similar to NUnit:
- Constraint based Assertion concept (Script Blocks {} as lambda expressions)
- Test functions
- Skip (Ignore) Attribute to temporarily exclude test functions from being executed
- Category Attribute to filter out functions that belong to a specific category and execute only these
- Expected Exception Attribute to test for error conditions
Features that are different:
- No Setup or Teardown functions
- No FixtureSetup or FixtureTeardown functions
- Test functions are organized and executed in a script file entity instead of a .NET class
Note: For installation instructions read the following article: http://www.tellingmachine.com/post/2009/08/PSUnit-PowerShell-Unit-Testing-Framework-ndash3b-Getting-Started-Guide-ndash3b-Version-2-Beta-1.aspx
Step 1: Create a new test script
I assume that you are already working on a script that you want to write unit tests for. I call this script “Script-Under-Test”. Open your Script-Under-Test in the PowerShell ISE and create a new empty script. I recommend to name your test script like your Script-Under-Test plus the “.Test.ps1” extension. This convention helps to organize your tests efficiently.
In my example the Script-Under-Test is called “Interpolate-Records.ps1” and I named the test script “Interpolate-Records.Test.ps1”.
Figure 1: Name your test script like your Script-Under-Test plus “.Test.ps1”
Step 2: Add script reference to the PSUnit framework and script under test
Add a reference to the PSUnit.ps1 script and to your Script-Under-Test.
1: . PSUnit.ps1
2: . (Join-Path -Path $env:PSUNIT_HOME -ChildPath "Samples\Interpolate-Records.ps1")
Figure 2: Add script references to PSUnit.ps1 and your Script-Under-Test
Step 3: Add a test function
Test functions are defined as such by prefixing them with the “Test.” string. I recommend to make the function names very descriptive. One way is to use the following pattern:
[Test.][Function that needs to be tested]_[Expected Result]_[Pre-conditions]
Example:
1: function Test.Is-RecordAtDesiredSampleTimeStamp_ReturnsFalseIfSecondsAreNot0()

Figure 3: Prefix your test function name with “Test.” This makes it a unit test.
Function Parameters
A basic test function can be attributed with three parameters. Custom square bracket attributes are not available in PowerShell and I decided to get this functionality with a very pragmatic approach. I am using function parameters to convey the meta data for specific test scenarios.
$ExpectedException Parameter (Attribute)
With the help of this function parameter the test runner can determine that an exception that the test throws is of the specified type. The parameter name is $ExpectedException and the parameter type is provided in the function parameter definition.
Here is an example test:
1: function Test.Is-RecordAtDesiredSampleTimeStamp_ThrowsArgumentOutOfRangeExceptionIfDateIsInTheFuture([switch] $Category_ParameterValidation, [System.ArgumentOutOfRangeException] $ExpectedException = $Null)
2: {
3: #Arrange
4: $Time = ([DateTime]::Now).AddDays(1)
5:
6: #Act
7: $Actual = Is-RecordAtDesiredSampleTimeStamp -Time $Time -SampleInterval 10
8:
9: #Assert
10: }
$Category_xyz Parameter (Attribute)
You can decorate a test with a specific category. If you tell the test runner to execute tests with a specific category, then only test that have a category parameter with the specified name will be executed. The parameter name has two parts. It starts with $Category_ and ends with the category name. E.g. $Category_FastTests. The category specified with this parameter is “FastTests”. The parameter is of type switch.
1: function Test.Is-RecordAtDesiredSampleTimeStamp_DoesntThrowArgumentOutOfRangeExceptionIfSampleIntervalIs12AndTimeIsYesterday([switch] $Category_ParameterValidation)
2: {
3: #Arrange
4: $Time = ([DateTime]::Now).AddDays(-1)
5:
6: #Act
7: $Actual = Is-RecordAtDesiredSampleTimeStamp -Time $Time -SampleInterval 12
8:
9: #Assert
10: }
$Skip Parameter (Attribute)
A test function with the $Skip parameter will not be executed by the test runner. The test will be ignored. The name of the parameter is $Skip and it is of type switch.
1: function Test.Change-Status_ThrowsInvalidOperationExceptionIfStatusTransitionsFrom2To4([switch] $Skip)
2: {
3:
4: #Arrange
5: $OldStatus =2
6: $NewStatus =4
7:
8: #Act
9: Change-Status -OldStatus $OldStatus -NewStatus $NewStatus
10:
11: #Assert
12: }
These three attributes can be used at the same time and the order doesn’t matter. Here is an example unit test that specifies a category and an expected exception attribute.
1: function Test.Is-RecordAtDesiredSampleTimeStamp_ThrowsArgumentOutOfRangeExceptionIfSampleIntervalIs-12([switch] $Category_ParameterValidation, [System.ArgumentOutOfRangeException] $ExpectedException = $Null)
2: {
3: #Arrange
4: $Time = ([DateTime]::Now).AddDays(-1)
5:
6: #Act
7: $Actual = Is-RecordAtDesiredSampleTimeStamp -Time $Time -SampleInterval -12
8:
9: #Assert
10: }
Writing PSUnit tests (Continued)
Step 4: Follow the AAA pattern: Arrange, Act and Assert
This pattern is widespread and will help you to structure your tests nicely.
- Arrange: Initialize variables
- Act: Call the function under test
- Assert: Verify the result of the tested function
Step 5: Add an Assert constraint
One of the core features of any unit testing framework is the assertion library. This framework followed a pragmatic and also very powerful approach to not provide a library of different assertions, but rather take advantage of the fact that PowerShell is a very dynamic language that supports lambda expressions in the form of Scriptblocks. The only assertion function in PSUnit is called Assert-That and takes only two parameters:
- $Actual is the input object that a constraint is going to be applied to
- $Constraint is a Scriptblock that represents the constraint that $Actual is going to be evaluated against
Assert-That returns either a Boolean $True or throws an exception that gets analyzed by the test runner.
Here is the example of a very simple constraint:
1: function Test.Is-RecordAtDesiredSampleTimeStamp_ReturnsFalseIfSecondsAreNot0()
2: {
3: #Arrange
4: $Time = New-Object -TypeName "System.DateTime" -ArgumentList 2009,1,1,10,45,01
5: #Act
6: $Actual = Is-RecordAtDesiredSampleTimeStamp -Time $Time -SampleInterval 10
7: #Assert
8: Assert-That -ActualValue $Actual -Constraint {$ActualValue -eq $false}
9: }
Figure 4: Simple Assert constraint
The beauty of the constraint based assertion model is that it can be easily extended and the constraints can be as specific and complex as you wish.
Here is an example constraint that verifies that the elements of a collection are in the same order than the elements of a reference collection:
1: Function Test.GetEnumerator_UsingForceSwitchWithHashTableDoesntChangeOutcome([switch] $Category_HashTable)
2: {
3: #Arrange
4: $Team = @{4 = "Joe"; 2 = "Steve"; 12 = "Tom"}
5:
6: #Act
7: $Actual = $Team | Get-Enumerator -Force | Sort-Object -Property "Key" | Foreach-Object{ $_.key}
8:
9: #Assert
10: Write-Debug $($Actual.GetType().FullName)
11: $ExpectedOrder = 2,4,12
12: Assert-That -ActualValue $Actual -Constraint {(Compare-Object -ReferenceObject $ExpectedOrder -DifferenceObject $ActualValue -IncludeEqual -SyncWindow 0).Count -eq 3}
13: }
Step 6: Run your tests
Run your test function by making your test script the current script in the PowerShell ISE and select the PowerShell ISE command Custom/Run Unit Tests (CTP3) or AddOns/Execute Unit Tests (RTM).
Use your console output to verify whether your test did what you expected it to do.
Figure 5: Test result report
Questions and comments
Any feedback is well come. Post a comment to this blog or leave a comment on the Codeplex PSUnit site: http://www.psunit.org.