In Pester 4.5.0 a new detailed code coverage analysis file can be created which unlocks some nice features both for CI and development. In this post we are going to look at using coverage gutters to get insight in to which lines of code are covered by pester tests.

Being able to have coverage gutters for PowerShell in VSCode has been an issue in the vscode-powershell repo back since February 2017. Thanks to the coverage gutters extension and the new detailed coverage analysis report, this is now possible.

Setting up your environment

To be able to use coverage gutters, you first need to install the Coverage Gutters extension and make sure you are have at least Pester 4.5.0 installed.

code --install-extension ryanluker.vscode-coverage-gutters
Install-Module pester -SkipPublisherCheck -Scope CurrentUser -Force -MinimumVersion 4.5.0

Creating the coverage report

Before we proceed, a caveat to using the coverage markers is that files that you want to get coverage markers for need to exists in a subdirectory to the root of your workspace. This is due to how the coverage xml files are built and how paths to coverage files are derived. I.e having a workspace that looks like this won’t work:

└───workspaceroot
        script.ps1

However, this would work:

└───workspaceroot
    └───source
            script.ps1

Hopefully this will be fixed in a future version of Coverage Gutters.

Now to generate your coverage report, you need to create an array of file paths that you want to be included in your coverage report, then run Invoke-Pester specifying cov.xmlas the name of the code coverage report.

From your workspace root:

$coverageFiles = Get-ChildItem -path .\Source\*.ps1 | Select-Object -ExpandProperty FullName
Invoke-Pester -CodeCoverage $coverageFiles  -CodeCoverageOutputFile cov.xml

Once the tests have run, and the report is generated, click the “Watch” button in the VSCode status bar: watch-icon

Now, you should have markers next to each line of code in your source files, with green showing lines which have been hit by tests and red showing lines which have been missed.

Automating report creation with VSCode tasks

I have put all the following code, along with dummy module, into a github repo here. Go ahead and clone it to get a live demonstation.

When writing code, manually running testing tasks everytime you make a change is cumbersome. But thankfully using tasks in vscode in combination with a build script makes it simple. When I develop modules I have seperate folders under the workspace root for the source code and the pester tests. Each function then get’s its own ps1 file along with a test file, so it looks like this:

workspaceroot
├───source
│   │   MyModule.psd1
│   │   MyModule.psm1
│   │
│   ├───private
│   │       _doInternalThing.ps1
│   │
│   └───public
│           New-Thing.ps1
│
└───tests
        New-Thing.tests.ps1
        _doInternalThing.tests.ps1

In this example, for simplification, I’m running a vanilla PowerShell script to generate the coverage report, however, it’s recommended to make this a task of your build script using something like Invoke-Build or PSake.

Create a file in your workspace root named “Test-SingleFile.ps1” with the following contents:

param(
    [Parameter(Mandatory = $true)]
    [string]
    $FileToTest
)

$FileToTest = Resolve-Path $FileToTest
$fileName = Split-Path $FileToTest -leaf
if ($FileName -like "*.tests.ps1")
{
    $filename = $fileName -replace "tests\.ps1$", "ps1"
    $sourceDir = Join-Path $PSScriptRoot "Source"
    $functionFile = Get-ChildItem $sourceDir -Filter $fileName -Recurse | Select-Object -First 1 -ExpandProperty FullName
    $testFile = $FileToTest
}
else
{
    $fileName = $fileName -replace ".ps1$", ".tests.ps1"
    $testFile = (Join-Path $PSScriptRoot "/Tests/$fileName")
    $functionFile = $FileToTest
}

Invoke-Pester -Script $testFile -CodeCoverage $functionFile -CodeCoverageOutputFile "$PSScriptRoot\cov.xml"

Now, create a file called .vscode\tasks.json with the following contents:

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "windows": {
        "options": {
            "shell": {
                "executable": "C:\\Program Files\\PowerShell\\6\\pwsh.exe",
                "args": [
                    "-NoProfile",
                    "-ExecutionPolicy",
                    "Bypass",
                    "-Command"
                ]
            }
        }
    },
    "linux": {
        "options": {
            "shell": {
                "executable": "/usr/bin/pwsh",
                "args": [
                    "-NoProfile",
                    "-Command"
                ]
            }
        }
    },
    "osx": {
        "options": {
            "shell": {
                "executable": "/usr/local/bin/pwsh",
                "args": [
                    "-NoProfile",
                    "-Command"
                ]
            }
        }
    },
    "tasks": [
        {
            "label": "Test",
            "type": "shell",
            "command": "./Test-SingleFile.ps1 -FileToTest ${file}",
            "group": {
                "kind": "test",
                "isDefault": true
            },
            "problemMatcher": [
                "$pester"
            ]
        },
    ]
}

Now when you run the test task in VSCode, it will only run the tests for the active file. This makes it much easier for quick feedback during development.

coverage-gutters-demo

The coverage report created will only contain the coverage for the active file, so if you switch to a different file/function, you will need to re-run the test task to generate the coverage gutters.

Happy testing!