When testing PowerShell modules and scripts, the de facto standard is Pester. Pester is a unit testing framework that allows a developer to pick apart and confirm various bits and pieces of PowerShell code.
To run a test, the Pester Wiki will tell you to run New-Fixture, which will create some template code, fill in the template code with your tests and then run Invoke-Pester to execute the tests. Although this works fine when running tests across a few scripts or modules, this approach doesn’t scale well when testing dozens or hundreds of different modules with potentially thousands of functions inside. This is when it’s time to build a small framework around Pester custom-built for your organization’s needs.
As an example of this framework, let’s cover a situation where an organization might have many different modules containing lots of functions. Because just performing unit tests don’t cover all scenarios, this organization has chosen to use Pester to perform three different kinds of tests; unit, integration and acceptance. Each of these types of tests performs tests on different “layers” of the code thus are built differently.
The first task is figuring out how to separate out each test category. My recommended method is to use different files and to place these files in the directory where each of the modules being tested resides. For example, if testing a module called Foo, the file structure might look something like this:
C:Program FilesWindowsPowerShellFoo
–> Foo.psm1
–> Foo.psd1
–> Foo.Unit.Tests.ps1
–> Foo.Integration.Tests.ps1
–> Foo.Acceptance.Tests.ps1
If this naming convention is followed across all modules, it’s now possible to make assumptions as to where the unit, integration and acceptance tests are stored for each module.
We can now build a layer of abstraction on top of Pester with custom functions better representing the tasks we’d like to invoke. For example, if I’d like to run my unit tests on the Foo module, by just using Invoke-Pester, I might have to do something like this:
Invoke-Pester -Path C:Program FilesWindowsPowerShellFooFoo.Unit.Tests.p1
This forces me to type out that entire path and specify the file name. I don’t want to do that. I just want to say “run my unit tests on the Foo module.” By starting to add some functions in my custom test framework, I can make it happen. I could be able to do something like this instead:
Start-UnitTest -Module Foo
This is much easier and more intuitive. To do this will require three primary functions; Start-UnitTest, Start-IntegrationTest, and Start-AcceptanceTest. I’ll also need another function called Find-TestScript to find the tests file and finally Start-PesterTest, which is a generic function that cuts down on code duplication between the primary functions.
To get started, I’ll create a module called PesterTest and build each of these functions inside.
function Start-PesterTest {
param(
[string]$Module,
[string]$Type,
[string]$TestName,
[hashtable]$AdditionalParams
)
}function Find-TestScript {
param(
[string]$Module,
[string]$Type
)
}function Start-UnitTest {
param(
[string]$Module,
[string]$TestName,
[hashtable]$AdditionalParams
)
}function Start-IntegrationTest {
param(
[string]$Module,
[string]$TestName,
[hashtable]$AdditionalParams
)
}function Start-AcceptanceTest {
param(
[string]$Module,
[string]$TestName,
[hashtable]$AdditionalParams
)
}
I have not included the entire codebase for this module here in the article due to length concerns. To grab a copy of this module, download it from my Github repository.
Once you’ve got the module, we can now quickly find any test we like, as well as pass parameters directly to the Invoke-Pester command.
Start-UnitTest -Module Foo
Start-UnitTest -Module Foo -TestName Foo-BarFunction
Start-UnitTest -Module Foo -TestName Foo-BarFunction -AdditionalParams @{Tag = ‘Baz’}
Under the covers, these functions are still using Invoke-Pester but add a layer of abstraction from it to more quickly run and discover each of your unique types of tests.
This module should provide a foundation for building a test framework. Undoubtedly, it will not meet everyone’s requirements exactly. However, since this is just PowerShell code, add additional functionality and make it fit your organization’s needs.