Как использовать многопоточность PowerShell и модульное тестирование с помощью Pester Mocks
Я пытаюсь сделать простую параллельную операцию в Powershell. Я использую PoshRSJobs для многопоточности, хотя я также пробовал Invoke-Parallel с той же проблемой. Мне нужно вызвать пару моих собственных функций в скриптовом теле задания, но это не позволяет мне СОЗДАТЬ эти функции для модульного тестирования (в итоге они являются исходными немодальными функциями). На данный момент, я просто пытаюсь утверждать, что они были вызваны правильное количество раз.
Вот исходный класс (функциональность импортированных модулей не имеет значения - фактические реализации в настоящее время возвращают тестовые строки)...
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
}
Вот тест Пестера...
$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
}
}
Вот результат теста Пестера в настоящее время...
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 и при этом иметь возможность их макетировать и тестировать.
2 ответа
Я не считаю это полным ответом, и я не работаю над проектом Pester, но я бы сказал, что это просто не поддерживаемый сценарий для Pester. Это может измениться, если / если параллельное программирование станет частью собственно PowerShell (или не может).
Если вы хотите изменить свою реализацию, вы можете обойти это ограничение, чтобы поддержать какое-то тестирование.
Например, может быть, ваша функция не использует RSJob, когда у нее есть только одна вещь (что может быть удобно при тестировании).
Или, может быть, вы реализуете -Serial
или же -NoParallel
или же -SingleRunspace
переключатель (или -ConcurrencyFactor
который вы установили 1
в тестах), где вы не используете пространство для выполнения этих условий.
Исходя из вашего примера, трудно сказать, адекватно ли тестирует этот вид то, что вы хотите, но похоже, что он это делает.
Мне удалось заставить его работать, внедрив макет в поток; вот профи концепции, но мелкие детали должны быть выработаны в каждом конкретном случае
#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'