纠缠单元测试 - 模拟 Get-ADUser 未按预期工作

Pester Unit Test - Mock Get-ADUser Not Working as Expected

首先,如果这不是提出这些问题的正确方式,我会道歉,这是我第一次提交给 SO。

简短版本是:

我有一个脚本 Get-SamAccountName.ps1,它接受 -FirstName, -LastName, -Format 的输入变量,然后根据格式生成一个新的 SamAccountName。现在函数本身工作得很好(针对 AD 对象进行测试以验证)。

然而,当我尝试为此编写一些 Pester 测试时,我遇到了一些麻烦,特别是围绕 Pester 的 Mock 函数。

函数Get-SamAccountName的相关部分如下(去除了参数块等不相关的项目):

function Get-SamAccountName {
    param(
        [string[]]$FirstName,
        [string[]]$LastName,
        [string[]]$Format
    )

................

    # If SamAccountName already in use, add a number until you find a free SamAccountName to use.
    if (Get-ADUser -Filter {samaccountname -eq $BaseSam} -ErrorAction SilentlyContinue) {
        $index = 0
        do {
            $index++
            $SamAccountName = "{0}{1}" -f $BaseSam.ToLower(),$index
        } until (-not(Get-ADUser -Filter {samaccountname -eq $SamAccountName} -ErrorAction SilentlyContinue))
    } else {
        $SamAccountName = $BaseSam.tolower()
    }
    return $SamAccountName
}

函数的这一部分是使用 Get-ADUser 的地方,也是我试图在我的 Pester 测试中模拟的内容。本质上是根据名字和姓氏生成的基本 SamAccountName $BaseSam,然后根据 AD 检查它是否存在,如果它确实存在,则在每次迭代的 SamAccountName 末尾添加一个 #。

我的 Pester 测试文件如下所示,与上面一样去掉了其他有效的测试(以第一个为例):

$ModuleRoot = $env:BHModulePath
$ModuleName = $env:BHProjectName
$ModulePath = $env:BHPSModuleManifest
Remove-Module -Name $ModuleName -ErrorAction 'SilentlyContinue'
Import-Module $ModulePath -Force

InModuleScope $ModuleName {
    BeforeAll {
        # Load Public function
        . "$ModuleRoot\Public\Get-SamAccountName.ps1"

        function Get-ADUser ($Filter) {}
    }

    Describe -Tags ('Unit') "Unit Tests for Get-SamAccountName" {
        #Run tests based upon the Examples from the Function Docs
................

        Context -Tags ('Unit') "Using the -FLast Format" {
            It "Returns <SamAccountName> SamAccountName from FirstName <FirstName> and LastName <LastName> Inputs in the -FLast format." -TestCases @(
                #Test with string containing Diacritic characters.
                @{ FirstName = "Clärk"; LastName = "Ként"; SamAccountName = "ckent"}
                #Test with string containing Latin characters only.
                @{ FirstName = "Bruce"; LastName = "Wayne"; SamAccountName = "bwayne"}
            ) {
                Get-SamAccountName -FirstName $FirstName -LastName $LastName -Format 'FLast' | Should -Be $SamAccountName
            }
        }

................

        Context -Tags ('Unit') "A User Already Exists in Active Directory" {
            It "Returns <expected> SamAccountName from FirstName <FirstName> and LastName <LastName> with an increment number." -TestCases @(
                #Test with string containing Diacritic characters.
                @{ FirstName = "Clärk"; LastName = "Ként"; BaseSam = "ckent"; expected = "ckent1"}
                #Test with string containing Latin characters only.
                @{ FirstName = "Bruce"; LastName = "Wayne"; BaseSam = "bwayne"; expected = "bwayne"}
            ) {
                Mock Get-ADUser -MockWith { $BaseSam } -ParameterFilter { $Filter -eq {samaccountname -eq $BaseSam} }
                Get-SamAccountName -FirstName $FirstName -LastName $LastName -Format 'FLast' | Should -Be $expected
            }
        }
    }
}

现在,我现在故意有不同的预期,因为我想看到一个测试,其中一个失败,一个通过以验证它是否已正确设置,最终结果都设置为 username1(附加的 1 用于验证已发现现有 AD 用户)。

运行 这在技术上与上面一样工作,但它与我想要实现的相反(下面的纠缠输出):

Describing Unit Tests for Get-SamAccountName
Context A User Already Exists in Active Directory
[-] Returns ckent1 SamAccountName from FirstName Clärk and LastName Ként with an increment number. 12ms (10ms|2ms)
    Expected strings to be the same, but they were different.
    Expected length: 9
    Actual length:   8
    Strings differ at index 8.
    Expected: 'ckent1'
    But was:  'ckent'
            --------^
    at Get-SamAccountName -FirstName $FirstName -LastName $LastName -Format 'FLast' | Should -Be $expected, /mnt/k/Repositories/CodeBlue.UserAutomation/Tests/Unit/Get-SamAccountName.Unit.Tests.ps1:70
    at <ScriptBlock>, /mnt/k/Repositories/CodeBlue.UserAutomation/Tests/Unit/Get-SamAccountName.Unit.Tests.ps1:70
[+] Returns bwayne SamAccountName from FirstName Bruce and LastName Wayne with an increment number. 9ms (8ms|1ms)
Tests completed in 281ms
Tests Passed: 1, Failed: 1, Skipped: 0 NotRun: 8

这 100% 可能是我的语法或项目的位置问题,我现在只使用 Pester 大约一个星期,从技术上讲,这是我第一次尝试使用 Mock。在这里阅读了无数关于 SO 的主题以及带有示例的博客条目,但我终其一生都无法让这个特定的用例按预期工作:(

我会注意到以上只是我尝试过的 Mock 的一次迭代,但是我设法获得的唯一一个 'working' 尽管与我尝试做的方式相反.

如有任何帮助,我们将不胜感激 <3

这很棘手。在我看来有两个问题:

  1. 您的 Mock 没有触发,因为 -ParameterFilterGet-ADUser 的调用不匹配。
  2. 你需要你的测试以两种不同的方式模拟 Get-ADUser,这样当第一次使用它时 returns 一个结果但是当第二次调用时它 returns 什么都没有。

我已经创建了我认为可行的解决方案,但是因为让 -ParameterFilterGet-ADUser-Filter 参数一起工作似乎非常困难,所以我更改了您的函数,以便其中一个 Get-ADUser 调用包含 -Properties 参数,这样我就可以根据它来区分用途。另一种方法可能是在 Get-ADUser 周围创建两个包装函数,以便在第一次和第二次使用它时使用。

这是我的解决方案。我还必须编辑您的函数,因为我缺少您设置 $BaseSam 的部分。这适用于 Bruce Wayne 但不适用于 Clark Kent,因为它不处理外国字符。

BeforeAll {
    function Get-SamAccountName {
        param(
            [string]$FirstName,
            [string[]]$LastName,
            [string[]]$Format
        )
    
        $BaseSam = $FirstName[0] + $LastName

        # If SamAccountName already in use, add a number until you find a free SamAccountName to use.
        if (Get-ADUser -Filter { samaccountname -eq $BaseSam } -Properties SamAccountName -ErrorAction SilentlyContinue) {
            $index = 0
            do {
                $index++
                $SamAccountName = "{0}{1}" -f $BaseSam.ToLower(), $index
            } until (-not(Get-ADUser -Filter { samaccountname -eq $SamAccountName } -ErrorAction SilentlyContinue))
        }
        else {
            $SamAccountName = $BaseSam.tolower()
        }
        return $SamAccountName
    }

    function Get-ADUser {
        param( $Filter, $Properties)
    }
}

Describe -Tags ('Unit') "Unit Tests for Get-SamAccountName" {

    Context -Tags ('Unit') "A User Already Exists in Active Directory" {

        It "Returns <expected> SamAccountName from FirstName <FirstName> and LastName <LastName> with an increment number." -TestCases @(
            #Test with string containing Diacritic characters.
            @{ FirstName = "Clärk"; LastName = "Ként"; BaseSam = "ckent"; expected = "ckent1" }
            #Test with string containing Latin characters only.
            @{ FirstName = "Bruce"; LastName = "Wayne"; BaseSam = "bwayne"; expected = "bwayne1" }
        ) {
            Mock Get-ADUser -MockWith { $BaseSam } -ParameterFilter { $Properties -eq 'SamAccountName' }
            Mock Get-ADUser -MockWith { }

            Get-SamAccountName -FirstName $FirstName -LastName $LastName -BaseSam $BaseSam -Format 'FLast' | Should -Be $expected
        }
    }
}

请注意,我的示例在 BeforeAll 中包含您的功能,只是为了便于测试,您可以继续导入您的模块。