После некоторых экспериментов я смог найти несколько удовлетворительных ответов. По большей части ответ - да, с небольшой хитростью.
Примеры тестов будут следовать, но вот содержимое 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 оболочка для разработки и тестирования сценариев без риска того, что тесты попросят его сделать что-то реальное в случае аварии.