【问题标题】:How to use PowerShell multithreading and still unit test with Pester Mocks如何使用 PowerShell 多线程并使用 Pester Mocks 进行单元测试
【发布时间】:2022-01-07 06:07:29
【问题描述】:

我正在尝试在 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
}

这是 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 中执行并行操作的方法,并且仍然能够模拟和单元测试它们。

【问题讨论】:

    标签: multithreading powershell mocking pester


    【解决方案1】:

    我不认为这是一个完整的答案,我也不参与 Pester 项目,但我想说这根本不是 Pester 支持的方案。这可能会在concurrent programming becomes part of PowerShell proper 时/如果发生变化(或者可能不会)。

    如果您愿意更改您的实现,您也许可以围绕此限制编写代码以支持某种测试。

    例如,当你的函数只有一件事要做时,它可能不使用 RSJob(这在测试时可能很方便)。

    或者,也许您实现了 -Serial-NoParallel-SingleRunspace 开关(或在测试中设置为 1-ConcurrencyFactor),其中您不使用运行空间来满足这些条件。

    根据您的示例,很难判断这种测试是否足以测试您想要的东西,但似乎确实如此。

    【讨论】:

    • 我喜欢 -NoParallel 的想法!如果我无法弄清楚其他事情,那可能就是要走的路。目前在同事的推荐下研究 PowerShell 工作流。
    • @jrizzo 工作流有各种各样的限制,用 Pester 之类的东西测试会更加困难。此外,PowerShell Core 将不再支持工作流(Windows PowerShell 预计不会有未来的更新),因此我建议不要在新项目中使用工作流。
    【解决方案2】:

    我能够通过将模拟注入线程来让它工作;这是一个概念教授,但细节需要根据具体情况敲定

    #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'
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-13
      • 1970-01-01
      相关资源
      最近更新 更多