是否有与Python的doctest模块等效的PowerShell?

Tre*_*van 5 powershell

我刚刚遇到了doctestPython中的模块,它可以帮助您针对嵌入在Python文档字符串中的示例代码执行自动测试。最终,这有助于确保Python模块的文档与模块的实际行为之间的一致性。

PowerShell中具有等效功能,因此我可以在.EXAMPLEPowerShell内置帮助的各部分中测试示例?

这是我要尝试做的一个例子:

function MyFunction ($x, $y) {
    <#
    .EXAMPLE

    > MyFunction -x 2 -y 2
    4
    #>
    return $x + $y
}

MyFunction -x 2 -y 2
Run Code Online (Sandbox Code Playgroud)

gms*_*man 1

你可以做到这一点,尽管我不知道有任何一体化的内置方法可以做到这一点。

方法 1 - 创建脚本块并执行它

帮助文档是一个对象,因此可以用来索引示例及其代码。下面是我能想到的最简单的示例,它执行您的示例代码。

我不确定这是否是这样doctest的——这对我来说似乎有点危险,但这可能就是你想要的!这是最简单的解决方案,我认为会给您最准确的结果。

Function Test-Example {
    param (
        $Module
    )
    # Get the examples
    $examples = Get-Help $Module -Examples

    # Loop over the code of each example
    foreach ($exampleCode in $examples.examples.example.code) {
        # create a scriptblock of your code
        $scriptBlock = [scriptblock]::Create($exampleCode)

        # execute the scriptblock
        $scriptBlock.Invoke()
    }
}
Run Code Online (Sandbox Code Playgroud)

方法 2 - 解析示例/函数并进行手动断言

我认为一个可能更好的方法是解析您的示例并解析函数以确保其有效。缺点是这可能会变得相当复杂,特别是如果您正在编写复杂的函数。

下面是一些代码,用于检查示例是否具有正确的函数名称、参数和有效值。它可能会被重构(第一次处理[System.Management.Automation.Language.Parser])并且根本不处理高级功能。

如果您关心诸如Mandatory、等问题ParameterSetNameValidatePattern这可能不是一个好的解决方案,因为它需要大量扩展。

Function Check-Example {
    param (
        $Function
    )

    # we'll use this to get the example command later
    # source: https://vexx32.github.io/2018/12/20/Searching-PowerShell-Abstract-Syntax-Tree/
    $commandAstPredicate = {
        param([System.Management.Automation.Language.Ast]$AstObject)
        return ($AstObject -is [System.Management.Automation.Language.CommandAst])
    }

    # Get the examples
    $examples = Get-Help $Function -Examples

    # Parse the function
    $parsedFunction = [System.Management.Automation.Language.Parser]::ParseInput((Get-Content Function:$Function), [ref]$null, [ref]$null)

    # Loop over the code of each example
    foreach ($exampleCode in $examples.examples.example.code) {

        # parse the example code
        $parsedExample = [System.Management.Automation.Language.Parser]::ParseInput($exampleCode, [ref]$null, [ref]$null)

        # get the command, which gives us useful properties we can use
        $parsedExampleCommand = $parsedExample.Find($commandAstPredicate,$true).CommandElements

        # check the command name is correct
        "Function is correctly named: $($parsedExampleCommand[0].Value -eq $Function)"

        # loop over the command elements. skip the first one, which we assume is the function name
        foreach ($element in ($parsedExampleCommand | select -Skip 1)) {
            "" # new line

            # check parameter in example exists in function definition
            if ($element.ParameterName) {
                "Checking parameter $($element.ParameterName)"
                $parameterDefinition = $parsedFunction.ParamBlock.Parameters | where {$_.Name.VariablePath.Userpath -eq $element.ParameterName}

                if ($parameterDefinition) {
                    "Parameter $($element.ParameterName) exists"
                    # store the parameter name so we can use it to check the value, which we should find in the next loop
                    # this falls apart for switches, which have no value so they'll need some additional logic
                    $previousParameterName = $element.ParameterName
                }
            }
            # check the value has the same type as defined in the function, or can at least be cast to it.
            elseif ($element.Value) {
                "Checking value $($element.Value) of parameter $previousParameterName"
                $parameterDefinition = $parsedFunction.ParamBlock.Parameters | where {$_.Name.VariablePath.Userpath -eq $previousParameterName}
                "Parameter $previousParameterName has the same type: $($element.StaticType.Name -eq $parameterDefinition.StaticType.Name)"
                "Parameter $previousParameterName can be cast to correct type: $(-not [string]::IsNullOrEmpty($element.Value -as $parameterDefinition.StaticType))"
            }
            else {
                "Unexpected command element:"
                $element
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

方法 3 - 使用Pester(可能超出范围)

我认为这个有点偏离主题,但值得一提。Pester 是 PowerShell 的测试框架,具有在这里可能有用的功能。您可以有一个通用测试,它将脚本/函数作为参数,并对解析的示例/函数运行测试。

这可能涉及像方法 1 中那样执行脚本或像方法 2 中那样检查参数。Pester 有一个HaveParameter断言,允许您检查有关函数的某些内容。

HaveParameter文档,从上面的链接复制:

Get-Command "Invoke-WebRequest" | Should -HaveParameter Uri -Mandatory
function f ([String] $Value = 8) { }
Get-Command f | Should -HaveParameter Value -Type String
Get-Command f | Should -Not -HaveParameter Name
Get-Command f | Should -HaveParameter Value -DefaultValue 8
Get-Command f | Should -HaveParameter Value -Not -Mandatory
Run Code Online (Sandbox Code Playgroud)