如何使用 PowerShell 多线程并通过 Pester Mocks 进行单元测试
How to use PowerShell multithreading and still unit test with Pester Mocks
我正在尝试在 Powershell 中执行简单的并行操作。我正在使用 PoshRSJobs 进行多线程处理,尽管我也尝试过 Invoke-Parallel 解决同样的问题。
我需要在作业的脚本主体中调用几个我自己的函数,但这不允许我模拟这些函数以进行单元测试(它们最终成为原始的非模拟函数)。在这一点上,我只是想断言他们被调用的次数正确。
这是原文class(导入模块的功能无关紧要 - 实际实现当前正在返回测试字符串)...
Import-Module $PSScriptRoot\Convert-DataTable
Import-Module $PSScriptRoot\Get-History
Import-Module $PSScriptRoot\Get-Assets
Import-Module $PSScriptRoot\Write-DataTable
function MyStuff (
param(
[string]$serverInstance = "localhost\SQLEXPRESS",
[string]$database = "PTLPowerShell",
[string]$tableName = "Test"
)
$assets = Get-Assets
$full_dt = New-Object System.Data.DataTable
$assets | Start-RSJob -ModulesToImport $PSScriptRoot\Convert-FLToDataTable, $PSScriptRoot\Get-FLHistory {
$history = Get-History $asset
$history_dt = Convert-DataTable $history
return $history_dt.Rows
} | Wait-RSJob | Receive-RSJob | ForEach {
$full_dt.Rows.Add($_)
}
Write-DataTable $serverInstance $database $tableName $full_dt
}
这是 Pester 测试...
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here$sut"
Describe "MyStuff" {
BeforeEach {
Mock Get-Assets { return "page1", "page2"}
Mock Get-History { return "history" }
Mock Convert-DataTable {
$historyDT = New-Object System.Data.Datatable;
$historyDT.TableName = 'Test'
return ,$historyDT
}
Mock Write-DataTable {}
}
It "should do something" {
{ MyStuff } | Should -Not -Throw;
}
It "should call Get-FLAssetGrid" {
Assert-MockCalled Get-Assets 1
}
It "should call Get-FLHistory" {
Assert-MockCalled Get-History 2
}
It "should call Convert-DataTable" {
Assert-MockCalled Convert-DataTable 2
}
It "should call Write-DataTable" {
Assert-MockCalled Write-DataTable 1
}
}
这是目前 Pester 测试的输出...
Describing MyStuff
[+] should do something 1.71s
[+] should call Get-Assets 211ms
[-] should call Get-History 61ms
Expected Get-History to be called at least 2 times but was called 0 times
23: Assert-MockCalled Get-History 2
at <ScriptBlock>, myFile.Tests.ps1: line 23
[-] should call Convert-DataTable 110ms
Expected Convert-DataTable to be called at least 2 times but was called 0 times
26: Assert-MockCalled Convert-DataTable 2
at <ScriptBlock>, myFile.Tests.ps1: line 26
[+] should call Write-DataTable 91ms
所以最终,我正在寻找一种在 PowerShell 中执行并行操作并且仍然能够模拟和单元测试它们的方法。
我认为这不是一个完整的答案,我也没有从事 Pester 项目,但我想说这根本不是 Pester 支持的方案。这可能会改变 when/if concurrent programming becomes part of PowerShell proper(也可能不会)。
如果您愿意更改您的实现,您或许可以绕过此限制来支持某种测试。
例如,您的函数可能在只有 1 件事要做时不使用 RSJob(在测试时可能很方便)。
或者你可以实现一个 -Serial
或 -NoParallel
或 -SingleRunspace
开关(或者你在测试中设置为 1
的 -ConcurrencyFactor
),其中对于这些情况,您不使用运行空间。
根据您的示例,很难判断这种测试是否充分测试了您想要的内容,但看起来确实如此。
我能够通过将模拟注入线程来让它工作;这是一个概念教授,但需要根据具体情况确定细节
#code.ps1
function ToTest{
start-job -Name OG -ScriptBlock {return (Get-Date '1/1/2000').ToString()}
}
纠缠
#code.Tests.ps1
$DebugPreference = 'Continue'
write-debug 'Pester''ng: code.ps1'
#################################################################
. (join-path $PSScriptRoot 'code.ps1')
Describe 'Unit Tests' -Tag 'Unit' {
Mock start-job {
$NewSB = {
&{describe 'MockingJob:$JobName' {
Mock get-date {'got mocked'}
& {$ScriptBlock} | Export-Clixml '$JobName.xml'
}}
$out = Import-Clixml '$JobName.xml'
remove-item '$JobName.xml'
$out | write-output
}.ToString().Replace('$ScriptBlock',$ScriptBlock.ToString()).Replace('$JobName',$Name)
start-job -Name "Mock_$Name" -ScriptBlock ([ScriptBlock]::Create($NewSB))
} -ParameterFilter {$Name -NotMatch 'Mock'}
It 'uses the mocked commandlet' {
$job = ToTest
receive-job -Job $job -wait | should be 'got mocked'
remove-job -Job $job
}
}
$DebugPreference = 'SilentlyContinue'
我正在尝试在 Powershell 中执行简单的并行操作。我正在使用 PoshRSJobs 进行多线程处理,尽管我也尝试过 Invoke-Parallel 解决同样的问题。 我需要在作业的脚本主体中调用几个我自己的函数,但这不允许我模拟这些函数以进行单元测试(它们最终成为原始的非模拟函数)。在这一点上,我只是想断言他们被调用的次数正确。
这是原文class(导入模块的功能无关紧要 - 实际实现当前正在返回测试字符串)...
Import-Module $PSScriptRoot\Convert-DataTable
Import-Module $PSScriptRoot\Get-History
Import-Module $PSScriptRoot\Get-Assets
Import-Module $PSScriptRoot\Write-DataTable
function MyStuff (
param(
[string]$serverInstance = "localhost\SQLEXPRESS",
[string]$database = "PTLPowerShell",
[string]$tableName = "Test"
)
$assets = Get-Assets
$full_dt = New-Object System.Data.DataTable
$assets | Start-RSJob -ModulesToImport $PSScriptRoot\Convert-FLToDataTable, $PSScriptRoot\Get-FLHistory {
$history = Get-History $asset
$history_dt = Convert-DataTable $history
return $history_dt.Rows
} | Wait-RSJob | Receive-RSJob | ForEach {
$full_dt.Rows.Add($_)
}
Write-DataTable $serverInstance $database $tableName $full_dt
}
这是 Pester 测试...
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.', '.'
. "$here$sut"
Describe "MyStuff" {
BeforeEach {
Mock Get-Assets { return "page1", "page2"}
Mock Get-History { return "history" }
Mock Convert-DataTable {
$historyDT = New-Object System.Data.Datatable;
$historyDT.TableName = 'Test'
return ,$historyDT
}
Mock Write-DataTable {}
}
It "should do something" {
{ MyStuff } | Should -Not -Throw;
}
It "should call Get-FLAssetGrid" {
Assert-MockCalled Get-Assets 1
}
It "should call Get-FLHistory" {
Assert-MockCalled Get-History 2
}
It "should call Convert-DataTable" {
Assert-MockCalled Convert-DataTable 2
}
It "should call Write-DataTable" {
Assert-MockCalled Write-DataTable 1
}
}
这是目前 Pester 测试的输出...
Describing MyStuff
[+] should do something 1.71s
[+] should call Get-Assets 211ms
[-] should call Get-History 61ms
Expected Get-History to be called at least 2 times but was called 0 times
23: Assert-MockCalled Get-History 2
at <ScriptBlock>, myFile.Tests.ps1: line 23
[-] should call Convert-DataTable 110ms
Expected Convert-DataTable to be called at least 2 times but was called 0 times
26: Assert-MockCalled Convert-DataTable 2
at <ScriptBlock>, myFile.Tests.ps1: line 26
[+] should call Write-DataTable 91ms
所以最终,我正在寻找一种在 PowerShell 中执行并行操作并且仍然能够模拟和单元测试它们的方法。
我认为这不是一个完整的答案,我也没有从事 Pester 项目,但我想说这根本不是 Pester 支持的方案。这可能会改变 when/if concurrent programming becomes part of PowerShell proper(也可能不会)。
如果您愿意更改您的实现,您或许可以绕过此限制来支持某种测试。
例如,您的函数可能在只有 1 件事要做时不使用 RSJob(在测试时可能很方便)。
或者你可以实现一个 -Serial
或 -NoParallel
或 -SingleRunspace
开关(或者你在测试中设置为 1
的 -ConcurrencyFactor
),其中对于这些情况,您不使用运行空间。
根据您的示例,很难判断这种测试是否充分测试了您想要的内容,但看起来确实如此。
我能够通过将模拟注入线程来让它工作;这是一个概念教授,但需要根据具体情况确定细节
#code.ps1
function ToTest{
start-job -Name OG -ScriptBlock {return (Get-Date '1/1/2000').ToString()}
}
纠缠
#code.Tests.ps1
$DebugPreference = 'Continue'
write-debug 'Pester''ng: code.ps1'
#################################################################
. (join-path $PSScriptRoot 'code.ps1')
Describe 'Unit Tests' -Tag 'Unit' {
Mock start-job {
$NewSB = {
&{describe 'MockingJob:$JobName' {
Mock get-date {'got mocked'}
& {$ScriptBlock} | Export-Clixml '$JobName.xml'
}}
$out = Import-Clixml '$JobName.xml'
remove-item '$JobName.xml'
$out | write-output
}.ToString().Replace('$ScriptBlock',$ScriptBlock.ToString()).Replace('$JobName',$Name)
start-job -Name "Mock_$Name" -ScriptBlock ([ScriptBlock]::Create($NewSB))
} -ParameterFilter {$Name -NotMatch 'Mock'}
It 'uses the mocked commandlet' {
$job = ToTest
receive-job -Job $job -wait | should be 'got mocked'
remove-job -Job $job
}
}
$DebugPreference = 'SilentlyContinue'