Тест Pester Gherkin: «Не удается найти переменную с именем« PSBoundParameters »» - PullRequest
0 голосов
/ 31 октября 2018

Мне попалась служебная функция: Get-ParameterValues, @ Jaykul , а также слегка измененная версия elovelan . Я решил использовать версию elovelan, которая не объявляет параметры. Я могу подтвердить, что, насколько я тестировал в командной строке, функция работает как задумано.

Я включаю функцию в закрытый модуль, который пишу, как общую служебную функцию (конечно, с должным указанием авторства - спасибо @ jaykul и elovelan ! !) и поэтому я пытаюсь написать для него модульные тесты. Однако при выполнении тестов я получаю сообщение об ошибке Cannot find a variable with the name 'PSBoundParameters'.

У меня такое ощущение, что это связано не столько с Пестером, сколько с моим пониманием PowerShell. Я надеюсь, что кто-то здесь, в сообществе, сможет мне помочь.

Сбой теста Пестера с ошибкой Cannot find a variable with the name 'PSBoundParameters'.

Тестируемая функция находится здесь . Вот часть кода теста:

Файл функций

Feature: Get-ParameterValues

  As a PowerShell function author
  In order to more easily deal with function parameters
  I want to be able to automatically populate a dictionary
  containing the supplied function arguments or the parameter's
  default value if the argument was not supplied
  So that I can reduce boilerplate code when dealing with
  function parameters.

  Background: A test function with parameters

    Given a test function
      """
      function global:Test {
          [CmdletBinding()]
          param(
              [Parameter(Position = 0)]
              $ValueWithNoDefault,
              [Parameter(Position = 1)]
              $ValueWithDefault = 'default',
              [Parameter(Position = 2)]
              $ValueWithNullDefault = $null
          )

          $Parameters = (Get-ParameterValues)
          $Parameters
      }
      """

  Scenario Outline: Get-ParameterValues returns a dictionary with the expected key and value

    Given the value '<Value>' is provided for the '<Parameter>' parameter
     When 'global:Test' is invoked
     Then the hashtable '<ContainsAKey>' a key for the parameter
      And the resolved parameter value is '<ExpectedValue>'.

    Examples: Examples using parameter ValueWithNoDefault
      | Parameter            | Value         | Contains         | ExpectedValue    |
      | ValueWithNoDefault   | <No value>    | does not contain | <Does Not Exist> |
      | ValueWithNoDefault   | `$null        | contains         | `$null           |
      | ValueWithNoDefault   | { `$foo = 1 } | contains         | { `$foo = 1 }    |
      | ValueWithNoDefault   | `$false       | contains         | `$false          |
      | ValueWithNoDefault   | `$true        | contains         | `$true           |
      | ValueWithNoDefault   | ""            | contains         | ""               |
      | ValueWithNoDefault   | 0             | contains         | 0                |
      | ValueWithNoDefault   | @()           | contains         | @()              |
      | ValueWithNoDefault   | 1             | contains         | 1                |

Определения шагов

BeforeEachFeature {
    Import-Module -Name "${pwd}\path-to\My-Module.psd1" -Scope Global -Force

    filter global:ConvertFrom-TableCellValue {
        Param(
            [Parameter(Mandatory=$True, Position=0, ValueFromPipeline=$True)]
            [AllowNull()]
            [AllowEmptyString()]
            $Value
        )

        Process {
            if ($Value -eq '$null') {
                $Value = $null
            }

            switch -Regex ($Value) {
                '^"?\{\s*(?<ScriptBody>.*)\s*\}"?$'   { $Value = [ScriptBlock]::Create($Matches.ScriptBody) }
                '(?<StringValue>".*")'                { $Value = ($Matches.StringValue -as [string]) }
                '$?(?i:(?<Boolean>true|false))'       { $Value = [Boolean]::Parse($Matches.Boolean) }
                '^\d+$'                               { $Value = $Value -as [int] }
                default                               {  }
            }

            $Value
        }
    }
}

AfterEachFeature {
    $Module = Get-Module -Name "My-Module"
    if ($Module) {
        $Module.Name | Remove-Module
    }

    if (Test-Path "Function:\global:ConvertTo-Parameter") {
        Remove-Item Function:\global:ConvertTo-Parameter
    }
}

Given "a test function" {
    param ([string]$func)

    Invoke-Expression $func

    $f = Get-Item 'Function:\Test'
    $f | Should -Not -BeNull
}

Given "the value '(?<Value>.*)' is provided for the '(?<Parameter>\S*)' parameter" {
    param(
        [object]$Value,
        [string]$Parameter
    )

    $Value = ConvertFrom-TableCellValue $Value
    if ($Value -ne "<No value>") {
        $Context = @{ $Parameter = $Value }
    } else {
        $Context = @{}
    }
}

When "'(?<CmdletName>.*)' is invoked" {
    [CmdletBinding()]
    Param ([string]$CmdletName)

    # NOTE: I probably don't need the if block, but I added this during debugging of this issue...just-in-case.
    # NOTE: The test fails in this step definition at this point when it executes the `Test` function which calls Get-ParameterValues.
    if ($Context.Keys.Count -gt 0) {
        $actual = &$CmdletName @Context
    } else {
        $actual = &$CmdletName
    }

    Write-Debug $actual.GetType().FullName
}

Then "the hashtable '(?<ContainsAKey>(does not contain|contains))' a key for the parameter" {
    param([string]$ContainsAKey)

    ($Context.ContainsKey($Parameter) -and $actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')
}

Пример выходного теста

psake version 4.7.4
Copyright (c) 2010-2017 James Kovacs & Contributors

Executing InstallDependencies
Executing Init
Executing Test
Testing all features in 'C:\src\git\My-Module\Specs\features' with tags: 'Get-ParameterValues'

Feature: Get-ParameterValues
       As a PowerShell function author
       In order to more easily deal with function parameters
       I want to be able to automatically populate a dictionary
       containing the supplied function arguments or the parameter's
       default value if the argument was not supplied
       So that I can reduce boilerplate code when dealing with
       function parameters.

  Scenario: Get-ParameterValues returns a dictionary with the expected key and value
  Examples:Examples using parameter ValueWithNoDefault
    [+] Given a test function 59ms
    [+] Given the value '<No value>' is provided for the 'ValueWithNoDefault' parameter 187ms
    [-] When 'global:Test' is invoked 48ms
      at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
      74:     $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly

      From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 35
      Cannot find a variable with the name 'PSBoundParameters'.
    [-] Then the hashtable 'does not contain' a key for the parameter 201ms
      at <ScriptBlock>, C:\src\git\My-Module\Specs\features\steps\Utility\Get-ParameterValues.steps.ps1: line 38
      38:     ($Context.ContainsKey($Parameter) -and $actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')

      From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 36
      You cannot call a method on a null-valued expression.
    [?] And the resolved parameter value is '<Does Not Exist>'. 40ms
      Could not find implementation for step!
      At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 37

Может кто-нибудь сказать, почему словарь $PSBoundParameters не существует внутри функции Get-ParameterValues, даже если параметры передаются в функцию Test? (У меня есть ощущение, что это может иметь какое-то отношение к правилам области видимости PowerShell, поскольку у блока сценария When есть область действия «сценарий», которую я пытался преодолеть, убедившись, что функция Test была объявлена ​​в global сфера.)

Обновление

Таким образом, если идти по этому пути, может возникнуть проблема с областью PowerShell, которую я просто не понимаю, я изменил свой шаг Gherkin Given следующим образом:

Given a test function
      """
      function global:Test {
          [CmdletBinding()]
          param(
              [Parameter(Position = 0)]
              $ValueWithNoDefault,
              [Parameter(Position = 1)]
              $ValueWithDefault = 'default',
              [Parameter(Position = 2)]
              $ValueWithNullDefault = $null
          )

          $DebugPreference = 'Continue'
          if ($PSBoundParameters) {
              Write-Debug "`$PSBoundParameters = $(ConvertTo-JSON $PSBoundParameters)"
          } else {
              Write-Debug "Unable to find `$PSBoundParameters automatic variable."
          }
          $Parameters = (Get-ParameterValues)
          $Parameters
      }
      """

А вот результаты выполнения тестов для первых двух записей таблицы (см. Оригинальный файл функций выше):

psake version 4.7.4
Copyright (c) 2010-2017 James Kovacs & Contributors

Executing InstallDependencies
Executing Init
Executing Test
Testing all features in 'C:\src\git\My-Module\Specs\features' with tags: 'Get-ParameterValues'

Feature: Get-ParameterValues
       As a PowerShell function author
       In order to more easily deal with function parameters
       I want to be able to automatically populate a dictionary
       containing the supplied function arguments or the parameter's
       default value if the argument was not supplied
       So that I can reduce boilerplate code when dealing with
       function parameters.

  Scenario: Get-ParameterValues returns a dictionary with the expected key and value
  Examples:Examples using parameter ValueWithNoDefault
    [+] Given a test function 54ms
DEBUG: $Parameter: ValueWithNoDefault
DEBUG: $Value: <No value>
    [+] Given the value '<No value>' is provided for the 'ValueWithNoDefault' 
parameter 29ms
DEBUG: Cmdlet: Test
DEBUG: Context: {

}
DEBUG: $PSBoundParameters = {

}
    [-] When 'Test' is invoked 47ms
      at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
      74:     $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly

      From C:\src\git\My-Module\Specs\features\Utility\Get- ParameterValues.feature: line 41
      Cannot find a variable with the name 'PSBoundParameters'.
DEBUG: Context: {

}
DEBUG: actual:
    [+] Then the hashtable 'does not contain' a key for the parameter 61ms
    [?] And the resolved parameter value is '<Does Not Exist>'. 30ms
      Could not find implementation for step!
      At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 43
DEBUG: $Parameter: ValueWithNoDefault
DEBUG: $Value:
    [+] Given the value '$null' is provided for the 'ValueWithNoDefault' parameter 14ms
DEBUG: Cmdlet: Test
DEBUG: Context: {
    "ValueWithNoDefault":  ""
}
DEBUG: $PSBoundParameters = {
    "ValueWithNoDefault":  ""
}
    [-] When 'Test' is invoked 9ms
      at <ScriptBlock>, C:\src\git\My-Module\Public\Utility\Get-ParameterValues.ps1: line 74
      74:     $BoundParameters = Get-Variable -Scope 1 -Name PSBoundParameters -ValueOnly

      From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 41
      Cannot find a variable with the name 'PSBoundParameters'.
DEBUG: Context: {
    "ValueWithNoDefault":  ""
}
DEBUG: actual:
    [-] Then the hashtable 'contains' a key for the parameter 20ms
      at <ScriptBlock>, C:\src\git\My- Module\Specs\features\steps\Utility\Get-ParameterValues.steps.ps1: line 39
      39:     ($Context.ContainsKey('Parameter') -and 
$actual.ContainsKey($Context.$Parameter)) | Should -Be ($ContainsAKey -eq 'contains')
      From C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 42
      Expected $true, but got $false.
    [?] And the resolved parameter value is '`$null'. 5ms
      Could not find implementation for step!
      At And, C:\src\git\My-Module\Specs\features\Utility\Get-ParameterValues.feature: line 43

Как показано, я «сбросил» коллекцию $PSBoundParameters из функции Test, созданной на шаге Given, только если она не равна нулю / пуста / является «правдивой» (в противном случае я бы написал строку говоря, что не существует). В любом случае, даже если $null назначено параметру, $PSBoundParameters определено и существует внутри функции Test. Однако после вызова Get-ParameterValues внутри этой функции делается попытка извлечь переменную $PSBoundParameters из родительской области (в этом случае функция Test & mdash; которая уже установила, что переменная существует в этой области). Но все же я получаю сообщение об ошибке из определения шага теста Пестера. К сожалению, эти результаты смущают меня еще больше.

...