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

时间:2019-07-13 00:55:01

标签: powershell

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

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

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

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

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

MyFunction -x 2 -y 2

1 个答案:

答案 0 :(得分: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()
    }
}

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

我认为一种可能更好的方法是解析您的示例并解析函数以确保其有效。缺点是,这可能会变得非常复杂,尤其是在编写复杂函数的情况下。

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

如果您关心MandatoryParameterSetNameValidatePattern之类的东西,那么这可能不是一个好的解决方案,因为它将需要大量扩展。

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
            }
        }
    }
}

方法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