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

Importing Pester Results into PowerShell

Posted on April 24, 2020April 24, 2020

Last week, a PowerShell scripting challenge was posted on the Iron Scripter web site.  The idea was that when you run a Pester test, you can save the results to a specially formatted  XML file.

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!

Invoke-Pester C:\scripts\sample.test.ps1 -OutputFile d:\temp\results.xml -OutputFormat JUnitXml

I get this result.

pester-exportThe challenge was to write a PowerShell command that could take the XML file and recreate this output as much as possible. Here's my result.

pester-importI have added a metadata header, but otherwise I think this is a pretty good recreation. Are you curious how I did this? If you want to try your hand at the challenge first, stop reading now.

Spoiler Alert

I wrote my function to recreate Pester results using the JunitXML format. It would be nice at some point to modify the function to be able to handle both XML formats. The function I wrote has a Path parameter . I can specify a value or pipe a file to the command.

Param(
        [Parameter(position = 0, ValueFromPipeline, HelpMessage = "Specify the path to the JunitXML Pester test file.")]
        [ValidateNotNullorEmpty()]
        #verify the file exists and it is an XML file
        [ValidateScript({
            If ((Test-Path $_) -AND ($_.split('.')[-1] -eq 'XML' )) {
                $True
            }
            else {
                #display a custom validation error message
                Throw "The file could not be found or wasn't an XML file."
            }
        })]
        [Alias("pspath")]
        [string]$Path
    )

The parameter definition is using a few validation tests. The main test is to ensure that the file exists and that it ends in XML. I have other validation later to test if it is a JunitXML file. Yes, I know that I could technically export the results using any file extension, but I'm assuming XML plus I wanted to demonstrate this technique.

The other feature in the parameter is that I am creating my own custom error message. When you use [ValidateScript()], the scriptblock must give you True or False.  In my case, if the test fails, I am throwing a customized error message.

paramfail-1

PowerShell 7 handles this even nicer.

paramfail-2

At this point it becomes a matter of parsing the XML document.

Process {
        Write-Verbose "Processing $Path"
        [xml]$doc = Get-Content -path $Path

I can look inside the XML to determine if this is a junit file. If it isn't I'll bail out of the command.

if ($doc.testsuites.noNamespaceSchemaLocation -notmatch "junit") {
            Write-Warning "Could not import $Path. You must specify a Pester test result file using the JUnitXML format."
            #abort
            return
        }

Otherwise, I'm parsing the document to get the metadata information which is assembled in a here-string and written to the host using Write-Host. In fact, the entire function writes directly to the console using Write-Host. Nothing is written to the pipeline. Although I will show you an option later. For this challenge, using Write-Host is perfectly fine. Another option would be to write a version of my function for PowerShell 7 and use ANSI escapes for the colorization. I'll save that for another time or you can write it.

Parsing Tests

The tricky part was parsing the test results. I had this XML to work with.

<testcase name="Alpha.Should have a value over 1" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="0.465" />
    <testcase name="Alpha.Should fail on a null value" status="Failed" classname="C:\work\sample.test.ps1" assertions="0" time="0.190">
      <failure message="Expected $true, but got $false." />
    </testcase>
    <testcase name="Alpha.Should accept a mandatory Computername parameter" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="0.300" />
    <testcase name="Alpha.Testing.Should have a value of 4" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="0.894" />
    <testcase name="Alpha.Testing.Should run without error" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="1.150" />
    <testcase name="Bravo.Should have a value less than 1" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="0.525" />
    <testcase name="Bravo.Should export the gravitational constant" status="Skipped" classname="C:\work\sample.test.ps1" assertions="0" time="0.000">
      <skipped message="" />
    </testcase>
    <testcase name="Bravo.Should run without errors" status="Passed" classname="C:\work\sample.test.ps1" assertions="0" time="2.357" />
    <testcase name="Bravo.Should run with credentials" status="Pending" classname="C:\work\sample.test.ps1" assertions="0" time="0.000">
      <skipped message="" />
    </testcase>

The Name attribute is the key item. The first part, up to the first period, is the Describe block. In my sample this is Alpha and Bravo.  Anything after the period is the assertion or the It statement. Unless, there is an intermediate word like 'Testing'. This is a Pester Context element.

I tried several techniques to pull this text apart. Eventually I settled on using a regular expression pattern.

[regex]$rx = "(?^[\w-_\s]+(?=\.))\.((?(?<=\.)\w+(?=\.))\.)?(?(?<=\.).*)"

The pattern uses named captures to identify the Describe, Context and Assertion components.

Looking to learn more about regular expressions in PowerShell? I created an entire course on the subject for Pluralsight.

I use this pattern to turn each test case into a custom object.

$tests = $doc.testsuites.testsuite.testcase | Group-Object -property Name
        Write-Verbose "Processing $($tests.count) tests"

        #turn each test into an object using a regex to split
        $testobj = foreach ($test in $Tests) {
            $m = $rx.Matches($test.name)
            [pscustomobject]@{
                Describe  = $m.groups[2]
                Context   = $m.groups[3]
                Assertion = $m.groups[4]
                TestCase  = $test.group
            }
        } #foreach test

I found this to be much easier to work with. Now I can use commands like Group-Object and ForEach to process the results in an orderly fashion, writing results to the host based east test. I ended up creating a private helper function, _parseTestCase, that would write the result to the host. When you look at the code, you'll see I'm using a Switch statement to determine what symbol and foreground color to use.

For the summary, I grouped the TestCase node on the Status property as a hashtable. The hashtable keys will things like 'Failed' and 'Skipped'. This makes it easy to get the counts that I need to display.

#write the summary information to the host in color
        $testHash = $doc.testsuites.testsuite.testcase | Group-Object -property Status -AsHashTable -AsString

        Write-Host "Tests completed in $(New-TimeSpan -seconds $doc.testsuites.time)" -ForegroundColor White
        Write-Host "Tests Passed: $($doc.testsuites.tests), " -ForegroundColor White -NoNewline
        Write-Host "Failed: $($TestHash["Failed"].count), " -ForegroundColor Red -NoNewline
        Write-Host "Skipped: $($TestHash["Skipped"].count), " -ForegroundColor Yellow -NoNewline
        Write-Host "Pending: $($TestHash["Pending"].count), " -ForegroundColor gray -NoNewline
        Write-Host "Inconclusive: $($TestHash["Inconclusive"].count)" -ForegroundColor DarkGray

The complete code is on Github.

The function has an alias of 'iptr'. I haven't tested with a lot of different test files so there may be things I've overlooked. But for the tests I write, this appears to be working.

Write-Information

I have one more thing to show you. Even though my code is using Write-Host, not all is lost. Write-Host is also a wrapper for Write-Information. Which means when I run my command like this (using my alias)

PS C:\>; iptr S:\sample.pester-results.xml -InformationVariable out

I get a variable, $out.

informationvariable-1But this is actually a rich object.

PS C:\> $out[0..1] | select *


MessageData :
********************************************************************************
Test File : C:\work\sample.test.ps1
Test Result : C:\scripts\sample.pester-results.xml
Test Date : 4/13/2020 7:04:48 PM
Computername : DESKTOP1
********************************************************************************
Source : C:\scripts\Analyze-TestResult.ps1
TimeGenerated : 4/24/2020 12:57:33 PM
Tags : {PSHOST}
User : BOVINE320\Jeff
Computer : Bovine320
ProcessId : 17492
NativeThreadId : 26760
ManagedThreadId : 14

MessageData :
Describing Alpha
Source : C:\scripts\Analyze-TestResult.ps1
TimeGenerated : 4/24/2020 12:57:33 PM
Tags : {PSHOST}
User : BOVINE320\Jeff
Computer : Bovine320
ProcessId : 17492
NativeThreadId : 26760
ManagedThreadId : 14

I can do anything I want with this data. I could re-export what I needed to a json or xml file. Or send it to a text file. The format won't be the same without a little tweaking.  But again, I'll leave that exercise for you.

In the meantime, if you didn't create your own challenge solution, I hope you'll give this a try and let me know what you think. Enjoy your weekend.


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

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