nl.nlsw.TestSuite.psm1
# __ _ ____ _ _ _ _ ____ ____ ____ ____ ____ ___ _ _ ____ ____ ____ # | \| |=== |/\| |___ | |--- |=== ==== [__] |--- | |/\| |--| |--< |=== # # @file nl.nlsw.TestSuite.psm1 # @date 2022-10-17 # @copyright Ernst van der Pols, Licensed under the EUPL-1.2-or-later #requires -version 5.1 <# .SYNOPSIS Create a new test suite. .DESCRIPTION Create a new test suite object to perform a series of test case operations. .PARAMETER Name The name of the test suite. .PARAMETER Quiet Do not write the test case result(s) to the host. .NOTES @author Ernst van der Pols #> function New-TestSuite { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [string]$Name, [Parameter(Mandatory=$false)] [int]$IdColumnWidth = 10, [Parameter(Mandatory=$false)] [int]$NameColumnWidth = 60, [Parameter(Mandatory=$false)] [switch]$Quiet ) # log the tests $suite = [PSCustomObject][ordered]@{ 'Name' = $Name; 'Case' = new-object "System.Collections.Generic.List[PSCustomObject]"; 'Errors' = 0; 'IdColumnWidth' = $IdColumnWidth; 'NameColumnWidth' = $NameColumnWidth; 'Quiet' = $Quiet; } if ($PSVersionTable.PSVersion.Major -lt 6) { # PSv5.1+: Make Out-File/>/>> create UTF-8 files with BOM by default: $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8' } if (!$suite.quiet) { write-host ("{0,$($suite.IdColumnWidth)} {1}" -f "TestSuite",$suite.Name) -foregroundcolor Yellow } return $suite } <# .SYNOPSIS Perform a test case. .DESCRIPTION Execute an operation as part of a test suite, compare the output of the operation with the expected result, and store all this in a new entry in the test suite. .PARAMETER Suite The test suite to store the test case result into. .PARAMETER Operation The operation to execute. .PARAMETER ExpectedOutput The expected output of the operation. .PARAMETER PassThru Return the created test case in the pipeline. .PARAMETER Quiet Do not write the test case result to the host. .NOTES @date 2019-01-21 @author Ernst van der Pols #> function Test-Case { [CmdletBinding()] param ( [Parameter(Mandatory=$True, ValueFromPipeline = $True)] [PSCustomObject]$suite, [Parameter(Mandatory=$True, Position=0)] [string]$Name, [Parameter(Mandatory=$True, Position=1)] [scriptblock]$Operation, [Parameter(Mandatory=$false, Position=2)] [object]$ExpectedOutput, [Parameter(Mandatory=$false)] [switch]$PassThru, [Parameter(Mandatory=$false)] [switch]$Quiet ) begin { $verboseOn = ($VerbosePreference -ne 'SilentlyContinue') } process { # create a new test case $test = [PSCustomObject][ordered]@{ 'id'=$("T{0:d}" -f $suite.case.Count); 'name'=$Name; 'operation'=$Operation; 'expected'=$ExpectedOutput; 'output'=$null; 'result'=$null; 'error'=$null; } $suite.case.Add($test) try { # execute the operation $test.output = invoke-command $test.operation -verbose:$verboseOn # check the expected output if ($test.expected -is [System.Type]) { # an output object of a specific type is expected: check the type of the output if ($test.output -is $test.expected) { $test.result = "OK" $message = "expected $($test.expected)" } else { $test.result = "FAILED" $outputtype = if ($test.output) { $test.output.GetType() } else { $null } $test.error = "invalid output type $($outputtype)" $suite.Errors++ } } elseif ($test.output -ne $test.expected) { $test.result = "FAILED" $test.error = "invalid output value: $($test.output)" $suite.Errors++ } else { $test.result = "OK" } } catch [Exception] { # the 'output' of the test is an exception $test.output = $_.Exception; if (($test.expected -is [System.Type]) -and ($test.output -is $test.expected)) { # the exception is of the expected type $test.result = "OK" $message = "expected $($test.expected)" } else { $test.result = "FAILED" $test.error = $_ $suite.Errors++ } } if ($PassThru) { # return the performed test write-output $test } if (!$Quiet -and !$suite.quiet) { $color = if ($test.error) { $message=$test.error; "Red" } else { "Green" } write-host ("{0,$($suite.IdColumnWidth)} {1} {2} {3}" -f $test.id,$test.name.PadRight($suite.NameColumnWidth,'.'),$test.result,$message) -foregroundcolor $color } } } <# .SYNOPSIS Write the results of the test suite to the host. .DESCRIPTION Calculate and write the results of the test suite to the host (the information stream). .PARAMETER Suite The test suite to report on. .PARAMETER Passthru Write the suite to the pipeline. .PARAMETER Quiet Do not write the test suite result to the host. .NOTES @date 2019-01-21 @author Ernst van der Pols #> function Write-TestResult { [CmdletBinding()] param ( [Parameter(Mandatory=$True, ValueFromPipeline = $True)] [PSCustomObject]$Suite, [Parameter(Mandatory=$False)] [switch]$Passthru, [Parameter(Mandatory=$False)] [switch]$Quiet ) process { if (!$Quiet -and !$suite.quiet) { write-host ("{0,$($suite.IdColumnWidth)} test(s) executed: {1} failed" -f $suite.case.Count,$suite.Errors) -foregroundcolor Yellow } if ($PassThru) { write-output $suite } } } <# .SYNOPSIS Run the test suite on module nl.nlsw.TestSuite. A set of functions to run a series of module, unit, or other PowerShell test operations (test cases). .DESCRIPTION Calculate and write the results of the test suite to the host. .EXAMPLE Install the nl.lsw.TestSuite module with PowerShell 5.1 or above - Install the folder nl.nlsw.TestSuite with its contents on your $env:Path - Open PowerShell - Enable the execution of (local) scripts PS > Set-ExecutionPolicy RemoteSigned - Check which commands are now available from the new module PS > Get-Command -module nl.nlsw.TestSuite - Run as example the test suite test of the TestSuite PS > $test = Test-TestSuite - Inspect output and results from the test suite PS > $test.case | format-table .NOTES @date 2019-01-21 @author Ernst van der Pols #> function Test-TestSuite { [CmdletBinding()] param ( ) begin { $suite = New-TestSuite -Name "Test of nl.nlsw.TestSuite" } process { $suite | test-case "Simple numerical example" { 1 + 1 } 2 $suite | test-case "throw [System.Exception]" { throw [System.Exception] } $([System.Exception]) # test the module manifest $suite | test-case "module manifest" { Test-ModuleManifest "$PSScriptRoot\nl.nlsw.TestSuite.psd1" | out-null; $? } $true $suite | test-case "verbose test case" { write-verbose "script that writes verbose output" 4>&1 } ([System.Management.Automation.VerboseRecord]) -verbose:$true # now, test some functions of this module with itself $case = $suite | test-case "`$ts = New-TestSuite" { New-TestSuite "The inner test suite" -IdColumnWidth (10+$suite.IdColumnWidth) -NameColumnWidth ($suite.NameColumnWidth-20) } ([PSObject]) -passthru # test some properties of the created object in the previous test case (use the "output"-property of the returned test-case-object) $suite | test-case "`$ts.Name" { $case.output.Name } "The inner test suite" $suite | test-case "`$ts.IdColumnWidth" { $case.output.IdColumnWidth } (10+$suite.IdColumnWidth) $suite | test-case "`$ts.case.Count -eq 0" { $case.output.Case.Count } 0 # now, something rather complex: test the execution of a test case on the inner test suite $oktest = $suite | test-case "run test case 0" { $case.output | test-case "Simple numerical example" { 1+1 } 2 -passthru } ([PSObject]) -passthru $suite | test-case "`$ts.case[0].result is OK" { $oktest.output.result } "OK" $suite | test-case "run (failing) test case 1" { $case.output | test-case "Faulty numerical example" { 1+1 } 3 -passthru } ([PSObject]) $suite | test-case "`$ts.case[1].result is FAILED" { $case.output.case[1].result } "FAILED" $suite | test-case "`$ts.case.Count -eq 2" { $case.output.Case.Count } 2 $suite | test-case "`$ts.case.Errors -eq 1" { $case.output.Errors } 1 $tr = $suite | test-case "Write-TestResult" { $case.output | Write-TestResult -passthru } ([PSObject]) -passthru $suite | test-case "returned suite is the suite" { $tr.output -eq $case.output } $true } end { $suite | Write-TestResult -passthru } } Export-ModuleMember -function * |