From Fear to Confidence

Test-Driven Development for PowerShell

Tommy Becker
- Sun Oct 29 2023

How confident are you when you run a powerful script against a production environment for the first time? If you feel a knot in your stomach, you’re not alone. The “write, pray, and run” method of scripting is common, but it’s stressful and risky.

There’s a better way, borrowed from the world of software engineering: Test-Driven Development (TDD). TDD is a practice that turns that fear into confidence by making testing a core part of the writing process, not an afterthought.

The Red-Green-Refactor Cycle

The TDD workflow is a simple but transformative cycle:

  1. Red: Write a small, automated test for a piece of functionality you haven’t written yet. This test will obviously fail because the code doesn’t exist. This is the “Red” light.
  2. Green: Write the absolute minimum amount of code required to make that single test pass. Don’t add extra features. Just get to a “Green” light.
  3. Refactor: With the safety of a passing test, you can now clean up your code. Improve its structure, remove duplication, and make it more readable, all while ensuring the test still passes.

You repeat this cycle for every new piece of functionality, building both your code and a suite of tests at the same time.

TDD in PowerShell with Pester

For PowerShell, the go-to testing framework is Pester. It allows you to write tests for your scripts in a clear, readable format that describes what your code should do.

Let’s say we want to write a function Get-Greeting that returns “Hello, World!”.

Step 1: Red (Write a Failing Test)

First, we write the Pester test. We create a file named MyFunctions.Tests.ps1.

# In MyFunctions.Tests.ps1
# Import the function we are about to write
. "$PSScriptRoot\MyFunctions.ps1"

Describe 'Get-Greeting' {
    It 'Should return the string "Hello, World!"' {
        $expected = 'Hello, World!'
        $actual = Get-Greeting
        $actual | Should -Be $expected
    }
}

If we run this test now using Invoke-Pester, it will fail because Get-Greeting doesn’t exist. We are Red.

Step 2: Green (Write Code to Pass the Test)

Next, we create MyFunctions.ps1 and write the simplest possible code to make the test pass.

# In MyFunctions.ps1
function Get-Greeting {
    return "Hello, World!"
}

Now, when we run the Pester test, it passes. We are Green.

Step 3: Refactor

Our function is so simple there’s nothing to refactor. But if it were more complex, this would be the time to clean it up, knowing our test will tell us if we break anything.

Why Bother?

By building a suite of tests, you create a safety net for your code.

  • Confidence: You can change and improve your scripts knowing that your tests will instantly tell you if you’ve broken existing functionality.
  • Better Design: Writing tests first forces you to think about what you want your code to do before you think about how it will do it. This often leads to simpler, more focused functions.
  • Living Documentation: Your Pester tests describe exactly how your code is supposed to behave, serving as documentation that can’t become outdated.

Adopting TDD is a powerful step in leveling up your scripting. It helps you move from writing one-off scripts to building reliable, professional-grade automation.