Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Discovering Pester Tags Revisited

Posted on January 6, 2022January 6, 2022

Yesterday I shared some PowerShell code I wrote to discover tags in a Pester test. It works nicely and I have no reason to complain. But as usual, there is never simply one way to do something in PowerShell. I got a suggestion from @FrodeFlaten on Twitter on an approach using the new configuration object in Pester 5.2. I'll readily admit that I am still getting up to speed on the latest version of Pester. That's one of my goals for this year, so this was a great chance to learn something new.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

PesterConfiguration

In previous versions of Pester, you could control Invoke-Pester through a set of parameters. Now we can use a configuration object that contains all the test settings. You can create a default object with New-PesterConfiguration.

pester configuration object

The suggestion was to configure Pester to skip running all tests and pass the test object to the pipeline. The configuration object has a set of nested properties. Here's Run.

pester configuration run

I can now modify the configuration object.

$config.Run.SkipRun = $true
$config.run.path = 'C:\scripts\PSFunctionTools\tests\psfunctiontools.tests.ps1'
$config.run.PassThru = $True
$config.Output.Verbosity = "none"

The properties should be self-explanatory. Since I'm not really running the test, I don't need to see any test results. I'll run Invoke-Pester using the configuration object.

$r = Invoke-Pester -Configuration $config -WarningAction SilentlyContinue

I'm using the warning action to suppress any deprecation messages. Not really necessary, but it makes for a cleaner experience.

The Pester Test Object

Because I used a Passthru configuration, I get a result like this:

pester test object

I can get tags from all individual tests easily enough.

 $($r.tests.tag).Where({$_}).foreach({$_.toLower()}) | Select-Object -unique
pester test tags

I'm doing some filtering and processing to get a unique list of tags.

But containers like Describe and Context can also have tags.

What you see are all Describe blocks and their tags. But that first Describe block has a nested Context blog which also has a tag. I need to recurse through containers to identify their tags.

function _recurseBlock {
    Param([object]$block)
    $block.tag
    if ($block.blocks) {
        foreach ($item in $block.blocks) {
            _recurseBlock $item
        }
    }
}

foreach ($block in $r.containers.blocks) {
    _recurseBlock $block
}
pester container tags

The nested Contex block has a tag of "structure". Of course, I can process this list to get unique items. Combine these results with the test tags and I should have everything I need.

A Revised PowerShell Function

With all of this in mind, I wrote a revised version of my function.

#requires -version 7.1
#requires -module @{ModuleName = 'Pester';ModuleVersion='5.2'}

Function Get-PesterTag {
    #thanks to https://twitter.com/FrodeFlaten for the suggestion
    [cmdletbinding()]
    [OutputType("pesterTag")]
    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = "Specify a Pester test file",
            ValueFromPipeline
        )]
        [ValidateScript({
                #validate file exits
                if (Test-Path $_) {
                    #now test for extension
                    if ($_ -match "\.ps1$") {
                        $True
                    }
                    else {
                        Throw "The filename must end in '.ps1'."
                    }
                }
                else {
                    Throw "Cannot find file $_."
                }
            })]
        [string]$Path
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
        $config = New-PesterConfiguration
        $config.Run.SkipRun = $true
        $config.run.PassThru = $True
        $config.Output.Verbosity = "none"

        #a private helper function to recurse through Describe and Context test blocks
        function _recurseBlock {
            Param([object]$block)
            $block.tag
            if ($block.blocks) {
                foreach ($item in $block.blocks) {
                    _recurseBlock $item
                }
            }
        }
        #Initiate a list to hold tags
        $list = [system.collections.generic.list[string]]::new()

    } #begin

    Process {
        $Path = Convert-Path $path
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Getting tags from $Path"
        $config.run.path = $path
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Invoking Pester"
        $r = Invoke-Pester -Configuration $config -WarningAction SilentlyContinue

        #get Test tags and add unique items to the list
        $($r.tests.tag).Where({ $_ }).foreach({
                $t = $_.toLower();
                if (-Not ($list.Contains($t))) {
                    $list.add($t)
                } })

        #get block tags
        foreach ($block in $r.containers.blocks) {
            _recurseBlock $block | ForEach-Object {
                $t = $_.toLower()
                #add item to the list if not already there
                if (-Not ($list.Contains($t))) {
                    $list.add($t)
                }
            } #foreach tag
        } #foreach block

        if ($list.count -gt 0) {
            [pscustomobject]@{
                PSTypename = "pesterTag"
                Path       = $Path
                Tags       = $List | Sort-Object
            }
        }
        else {
            Write-Warning "No tags found in $Path"
        }
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end

} #close Get-PesterTag

The function still writes an object to the pipeline but it uses a different approach to get the same results. However, there are tradeoffs.

My first function using regular expressions runs very fast. It processed this test file in 0.54ms. This version using the Pester configuration object takes 0.9ms. Frankly, I'm not going to quibble over this minute difference. In fact, this new version is going to be more accurate as it is processing the actual test file. My version using the PowerShell AST might not differentiate between live code and something that might have been commented out (I'd have to test that to be sure) so I'll most likely stick with this new version.

In fact, now that I know more about the Pester configuration object a number of ideas are sparking. I'm confident I'll be back.

Oh, before I forget. This new function only works in PowerShell 7. At least as written. I think Microsoft made some changes under the hood on how it unrolls collections of objects which is how I can easily get the tag property. If you are stuck with Windows PowerShell, you would need to use the AST version. Or feel free to modify this code to work in Windows PowerShell. A big goal for me this year is to leave Windows PowerShell behind. I'd rather spend my time learning new things instead of struggling with backward compatibility. I hope you can move forward with me.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

1 thought on “Discovering Pester Tags Revisited”

  1. Pingback: Discovering Pester Tags Revisited - The Lonely Administrator - Syndicated Blogs - IDERA Community

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d