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.

If you want to learn more about Pester, I know of a book that might help. ;)

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 you're faced with managing hundreds or thousands of tests, 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 doesn'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. A recommended way to do this 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 Files\WindowsPowerShell\Foo\
--> 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.

You 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:

PS51> Invoke-Pester -Path C:\Program Files\WindowsPowerShell\Foo\Foo.Unit.Tests.ps1

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:

PS51> 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
    )
}
PesterTest.psm1

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.

PS51> Start-UnitTest -Module Foo
PS51> Start-UnitTest -Module Foo -TestName Foo-BarFunction
PS51> 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.

Summary

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.

Please comment below if you feel like I've missed anything important or made a mistake. I'll fix it ASAP!

Further Reading

Be sure to check out some other related posts!