能否使用Pester及Mock测试带状态变更的PowerShell脚本?
当然可以!即使所有逻辑都封装在单个.ps1脚本里,Pester 结合 Mock 完全能覆盖你提到的两类测试需求——既可以验证脚本接收的参数是否符合预期,也能拦截内部函数的调用,避免其修改系统状态,同时验证调用参数的正确性。下面针对你的StateChangingDemo.ps1给出具体的测试实现方案:
核心思路说明
因为脚本内的函数默认运行在脚本私有作用域,直接调用脚本时测试会话的 Mock 无法拦截内部函数,所以我们需要用一些技巧让脚本逻辑运行在测试会话的作用域内,同时不修改原脚本的内容。这里提供两种可行的方案:
方案一:将脚本包装为测试会话内的函数
这种方法无需修改原脚本,只需在测试中把原脚本内容包装成一个自定义函数,这样内部函数会加载到测试作用域,Mock 就能生效。
测试脚本:StateChangingDemo.Tests.ps1
BeforeAll { # 读取原脚本内容 $originalScriptContent = Get-Content $PSScriptRoot\StateChangingDemo.ps1 -Raw # 将原脚本包装成一个可在测试中调用的函数 $wrapperFunction = @" function Invoke-StateChangingDemo { $originalScriptContent } "@ # 在测试会话中执行包装函数,加载脚本逻辑 Invoke-Expression $wrapperFunction } Describe "StateChangingDemo.ps1 参数验证" { It "接收符合预期的对象数组参数,类型和结构正确" { # 准备测试用的请求数据 $testRequests = @( @{ RequestId = 1; TargetState = "Enabled" }, @{ RequestId = 2; TargetState = "Disabled" } ) # Mock 内部函数,避免实际执行系统变更 Mock ChangeSystemStateSomehow {} # 调用包装后的脚本函数,传入测试参数 Invoke-StateChangingDemo -stateChangeRequests $testRequests # 验证脚本参数的元数据(类型、可选性等) $paramDefinition = (Get-Command Invoke-StateChangingDemo).Parameters['stateChangeRequests'] $paramDefinition.ParameterType.Name | Should -Be "Object[]" $paramDefinition.IsMandatory | Should -Be $false # 验证转换后的请求被正确传入内部函数(假设脚本转换逻辑是原样返回请求) Assert-MockCalled ChangeSystemStateSomehow -Exactly $testRequests.Count -Scope It foreach ($req in $testRequests) { Assert-MockCalled ChangeSystemStateSomehow -ParameterFilter { $changeRequestParameter.RequestId -eq $req.RequestId -and $changeRequestParameter.TargetState -eq $req.TargetState } -Scope It } } It "正确处理空参数或null的情况" { Mock ChangeSystemStateSomehow {} # 传入null参数 Invoke-StateChangingDemo -stateChangeRequests $null # 验证内部函数未被调用 Assert-MockCalled ChangeSystemStateSomehow -Exactly 0 -Scope It } } Describe "StateChangingDemo.ps1 内部函数调用验证" { It "拦截内部函数调用,避免实际修改系统状态" { $testRequests = @(@{ RequestId = 3; TargetState = "Enabled" }) Mock ChangeSystemStateSomehow {} Invoke-StateChangingDemo -stateChangeRequests $testRequests # 验证Mock被调用,说明原函数未执行实际逻辑 Assert-MockCalled ChangeSystemStateSomehow -Exactly 1 -Scope It } It "传入转换后的正确参数给ChangeSystemStateSomehow" { # 假设脚本内的转换逻辑是将TargetState转为大写 $testRequests = @(@{ RequestId = 4; TargetState = "enabled" }) $expectedTransformed = @(@{ RequestId = 4; TargetState = "ENABLED" }) Mock ChangeSystemStateSomehow {} Invoke-StateChangingDemo -stateChangeRequests $testRequests # 验证内部函数接收到的是转换后的参数 Assert-MockCalled ChangeSystemStateSomehow -ParameterFilter { $changeRequestParameter.RequestId -eq $expectedTransformed[0].RequestId -and $changeRequestParameter.TargetState -eq $expectedTransformed[0].TargetState } -Exactly 1 -Scope It } }
方案二:创建脚本副本并添加测试钩子
这种方法通过在测试中创建原脚本的副本,添加一行将脚本内的变量暴露给测试会话的代码,从而直接验证脚本接收的原始参数和转换后的参数,无需复制脚本内的转换逻辑。
测试脚本:StateChangingDemo.Tests.ps1
BeforeAll { # 创建原脚本的副本到Pester的TestDrive(临时目录) $originalScriptPath = "$PSScriptRoot\StateChangingDemo.ps1" $testScriptPath = "$TestDrive\StateChangingDemo.ps1" Copy-Item -Path $originalScriptPath -Destination $testScriptPath # 向副本末尾添加代码,将脚本内的变量暴露给测试会话 Add-Content -Path $testScriptPath -Value @" # 测试钩子:将内部变量输出到测试会话的脚本级作用域 `$script:ReceivedOriginalRequests = `$stateChangeRequests `$script:TransformedRequests = `$transformedRequests "@ # 加载副本内的函数到测试会话 . $testScriptPath } Describe "StateChangingDemo.ps1 参数验证" { It "接收的参数值和结构与调用方传入的完全一致" { $testRequests = @( [PSCustomObject]@{ Id = 1; State = "Active" }, [PSCustomObject]@{ Id = 2; State = "Inactive" } ) Mock ChangeSystemStateSomehow {} # 测试副本脚本 & $testScriptPath -stateChangeRequests $testRequests # 直接验证脚本接收的原始参数 $script:ReceivedOriginalRequests | Should -Be $testRequests $script:ReceivedOriginalRequests.Count | Should -Be 2 $script:ReceivedOriginalRequests[0].GetType().Name | Should -Be "PSCustomObject" } } Describe "StateChangingDemo.ps1 内部函数调用验证" { It "内部函数接收到的是转换后的参数" { $testRequests = @(@{ Id = 5; State = "active" }) Mock ChangeSystemStateSomehow {} & $testScriptPath -stateChangeRequests $testRequests # 直接用脚本暴露的转换后参数验证函数调用 foreach ($transformedReq in $script:TransformedRequests) { Assert-MockCalled ChangeSystemStateSomehow -ParameterFilter { $changeRequestParameter -eq $transformedReq } -Scope It } } }
两种方案都不需要修改原脚本的内容,完全满足你的测试需求:
- 方案一更适合不需要直接访问脚本内部变量的场景,通过Mock的参数验证间接确认参数处理的正确性;
- 方案二更直观,可以直接获取脚本内的原始参数和转换后参数,适合需要精确验证参数转换逻辑的场景。
内容的提问来源于stack exchange,提问作者John Doggett
相关产品推荐
相关产品推荐

