Можно ли использовать Pester и / или Mock для проверки сценария .ps1? - PullRequest
0 голосов
/ 19 января 2020

Рассмотрим скрипт .ps1, который принимает параметры и вносит изменения в состояние. Есть два аспекта скрипта, которые я хотел бы протестировать:

  • Параметры, полученные от вызывающей стороны, набираются и сортируются в наборы параметров в соответствии с ожиданиями
  • Ключевые вызовы внутренних функций сделано правильно

Имея это в виду, рассмотрите этот сценарий StateChangingDemo.ps1:

    param([object[]]$stateChangeRequests)


    function ChangeSystemStateSomehow($changeRequestParameter)
    {
        # ... apply $changeRequestParameter to system state
    }

    $transformedRequests = $stateChangeRequests | % {
        # do some processing of requests
    }

    $transformedRequests | % {
        ChangeSystemStateSomehow $_
    }

Можно ли использовать Пестера и насмешки для:

  1. Перехватить значение $stateChangeRequests, чтобы проверить, что оно имеет ожидаемое значение и форму, основываясь на том, как оно вызывается?
  2. Перехватить вызовы ChangeSystemStateSomehow, чтобы убедиться, что a) функция не наносит ущерба во время процесса тестирования (особенно из-за дефектов) и б) вызывается функция с ожидаемыми значениями?

Примечание: я вижу, как перемещение фрагментов скрипта в модуль может помочь с тестированием , но для этого вопроса давайте предположим, что все в сценарии должно оставаться в сценарии.

1 Ответ

0 голосов
/ 20 января 2020

После некоторых экспериментов я смог найти несколько удовлетворительных ответов. По большей части ответ - да, с небольшой хитростью.

Примеры тестов будут следовать, но вот содержимое StateChangingDemo.ps1, с которым я разработал тесты:

    [CmdletBinding(DefaultParameterSetName='SetA')]
    param(
        [Parameter(ParameterSetName='SetA')]
        [object[]]$TypeA_Requests,

        [Parameter(ParameterSetName='SetB')]
        [switch]$TypeB_All
    )

    function ChangeSystemStateSomehow($changeRequestParameter)
    {
        # ... apply $changeRequestParameter to system state
        "system state was modified"
    }

    $transformedRequests = $TypeA_Requests | % {
        # do some processing of requests
        $_
    }

    $transformedRequests | % {
        ChangeSystemStateSomehow $_
    }

Подделка функции внутри .ps1

Если .ps1 вызывает функцию, которую необходимо смоделировать (например, ChangeSystemStateSomehow), версия функции должна существовать и подвергаться насмешке перед вызовом. ps1.

    Describe 'test StateChangingDemo.ps1' {
        Context 'no mock' {
            It 'needs to be mocked' {
                & .\StateChangingDemo.ps1 | Should -Be "system state was modified"
            }
        }

        Context 'no mock, but placeholder function defined' {
            function ChangeSystemStateSomehow {
                "placeholder function"
            }

            # Show that a function in current scope won't displace the one in the script
            It 'still needs to be mocked' {
                & .\StateChangingDemo.ps1 | Should -Be "system state was modified"
            }

        }

        Context 'function called by script is mocked' {
            # This is required because Mock can't work before a function by the same name exists.
            # However, the mock persists even though the function is later replaced.
            function ChangeSystemStateSomehow {
                "placeholder function"
            }

            Mock ChangeSystemStateSomehow {"fake execution"}

            It 'can have functions within mocked' {
                 & .\StateChangingDemo.ps1 | Should -Be "fake execution"
            }
        }

    }

Тестирование парсинга параметров в скрипте

Этот аспект тестирования скрипта использует два подхода.

Следует -HaveParameter

Первый подход это признание того, что Should -HaveParameter можно использовать непосредственно в скрипте:

Describe 'test StateChangingDemo.ps1' {
    Context 'no mock again' {       
        It 'has expected parameters' {
             Get-command .\StateChangingDemo.ps1 | Should -Not -BeNullOrEmpty 
             Get-command .\StateChangingDemo.ps1 | Should -HaveParameter TypeA_Requests -Type [object[]] 
             Get-command .\StateChangingDemo.ps1 | Should -HaveParameter TypeB_All -Type [switch] 
        }
    }
}

Второй подход - это смоделировать сам скрипт. Это не так просто:

    Context 'whole script mock attempted' {
        Mock .\StateChangingDemo.ps1 {"fake script execution"}

        It 'cannot be mocked directly' {
             .\StateChangingDemo.ps1 | Should -Be "system state was modified"
        }
    }

Вышеупомянутый проходной тест показывает, что даже если мы установили фиктивный сценарий, сценарий все еще выполняется при вызове.

Mocking the Script самой

Вместо этого мы создаем функцию, реализация которой основана на обработке сценария как блока сценария:

    Context 'whole script wrapped' {
        Set-Item function:fnStateChangingDemo ([ScriptBlock]::Create((get-content -Raw .\StateChangingDemo.ps1)))

        It 'has expected parameters' {
             Get-command fnStateChangingDemo | Should -Not -BeNullOrEmpty 
             Get-command fnStateChangingDemo | Should -HaveParameter TypeA_Requests -Type "object[]" -Because 'it has TypeA_Requests'
             Get-command fnStateChangingDemo | Should -HaveParameter TypeB_All -Type "switch" -Because 'it has TypeB_All'
        }

        Mock ChangeSystemStateSomehow {"fake execution"}

        It 'can still have functions within mocked when it is wrapped with a function' {
             & fnStateChangingDemo | Should -Be "fake execution"
        }
    }

Приведенный выше тест показывает, что функция представляет собой тот же интерфейс, что и сам сценарий, поэтому мы можем проверить параметры на это так же. Более того, теперь мы можем использовать макет для безопасного создания более сложных утверждений о разборе параметров:

    Context 'whole script wrapped and mocked' {       
        Set-Item function:fnStateChangingDemo ([ScriptBlock]::Create((get-content -Raw .\StateChangingDemo.ps1)))

        Mock fnStateChangingDemo {"fake script function execution"}

        It 'can be mocked when wrapped as function' {
             fnStateChangingDemo | Should -Be "fake script function execution"
        }

        It 'cannot accept conflicting parameters' {
            # Using multiple parameter sets is not allowed
            {fnStateChangingDemo -TypeA_Requests 1,2,3 -TypeB_All} | Should -Throw "Parameter set cannot be resolved"
        }
    }

Используя более сложные фиктивные тела, Assert-MockCalled и Assert-VerifiableMocks и фильтры параметров, мы можем использовать функцию mocked оболочка для разработки и тестирования сценариев без риска того, что тесты попросят его сделать что-то реальное в случае аварии.

...