Skip to content

Code coverage is not reported correctly in some circumstances #2308

Open
@johlju

Description

@johlju

Checklist

What is the issue?

When running tests against a module script file that has a using module statement at the top of module script file in some cicumstances the code coverage is not reported accuratly what the tests actually covers.

There are 4 workarounds for this issue

  1. (non feasible) comment the line using module in SqlServerDsc.psm1
  2. (non feasible) change the order the tests are run
  3. switch to use the new code coverage method (not feasible yet due to Code coverage fails in some circumstances when UseBreakpoints is $false #2306).
  4. in the tests for Assert-ManagedServiceType add a mock for ConvertFrom-ManagedServiceType.

But what I'm wondering is why I have to add the alternativ 4 workaround for it to work.

When running the code as written in the "How to reproduce" it does not cover the two line shown below. Doing any of the workarounds it will correctly cover 100%.

C:\source\DebugCodeCoverage2> Invoke-Pester -Configuration $pesterConfig
Pester v5.4.0

Starting discovery in 2 files.
Discovery found 4 tests in 33ms.
Starting code coverage.
Code Coverage preparation finished after 8 ms.
Running tests.

Running tests from 'C:\source\DebugCodeCoverage2\Assert-ManagedServiceType.Tests.ps1'
Describing Assert-ManagedServiceType
 Context When types match
   [+] Should not throw an exception 32ms (29ms|3ms)

Running tests from 'C:\source\DebugCodeCoverage2\ConvertFrom-ManagedServiceType.Tests.ps1'
Describing ConvertFrom-ManagedServiceType
 Context When translating to a normalized service types
   [+] Should properly map 'SqlServer' to normalized service type 'DatabaseEngine' 8ms (2ms|6ms)
   [+] Should properly map 'SqlAgent' to normalized service type 'SqlServerAgent' 2ms (2ms|1ms)
   [+] Should properly map 'Search' to normalized service type 'Search' 3ms (2ms|1ms)
Tests completed in 180ms
Tests Passed: 4, Failed: 0, Skipped: 0 NotRun: 0
Processing code coverage result.
Code Coverage result processed in 6 ms.
Covered 71.43% / 75%. 7 analyzed Commands in 1 File.
Missed commands:

File              Class Function                       Line Command
----              ----- --------                       ---- -------
SqlServerDsc.psm1       ConvertFrom-ManagedServiceType   55 $serviceTypeValue = 'SqlServerAgent'
SqlServerDsc.psm1       ConvertFrom-ManagedServiceType   62 $serviceTypeValue = 'Search'

Expected Behavior

The tests to pass and report 100% code coverage as the tests do individually.

Steps To Reproduce

File SqlServerDsc.psm1

# (non feasible) workaround 1 - Comment `using module` from SqlServerDsc.psm1
using module .\DependentClassModule.psm1

function Assert-ManagedServiceType
{
    [OutputType()]
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [Object]
        $ServiceObject,

        [Parameter(Mandatory = $true)]
        [ValidateSet('DatabaseEngine', 'SqlServerAgent', 'Search', 'IntegrationServices', 'AnalysisServices', 'ReportingServices', 'SQLServerBrowser', 'NotificationServices')]
        [System.String]
        $ServiceType
    )

    process
    {
        $normalizedServiceType = ConvertFrom-ManagedServiceType -ServiceType $ServiceObject.Type

        if ($normalizedServiceType -ne $ServiceType)
        {
            # Removed to minimize code
        }
    }
}

function ConvertFrom-ManagedServiceType
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [System.String]
        $ServiceType
    )

    process
    {
        switch ($ServiceType)
        {
            'SqlServer'
            {
                $serviceTypeValue = 'DatabaseEngine'

                break
            }

            'SqlAgent'
            {
                $serviceTypeValue = 'SqlServerAgent'

                break
            }

            'Search'
            {
                $serviceTypeValue = 'Search'

                break
            }
        }

        return $serviceTypeValue
    }
}

File DependentClassModule.psm1

enum Ensure
{
    Present
    Absent
}

File Assert-ManagedServiceType.Tests.ps1

BeforeAll {
    Import-Module -Name './SqlServerDsc.psm1' -Force
}

AfterAll {
    # Unload the module being tested so that it doesn't impact any other tests.
    Get-Module -Name 'SqlServerDsc' -All | Remove-Module -Force
}

Describe 'Assert-ManagedServiceType' -Tag 'Private' {
    BeforeAll {
        $mockServiceObject = @{
            Type = 'SqlServer'
        }
    }

    Context 'When types match' {
        # ------
        # Workaround 3 - In tests for `Assert-ManagedServiceType` add a mock for `ConvertFrom-ManagedServiceType`.
        # ------

        # BeforeAll {
        #     Mock -CommandName ConvertFrom-ManagedServiceType -ModuleName 'SqlServerDsc' -MockWith {
        #         return 'DatabaseEngine'
        #     }
        # }

        It 'Should not throw an exception' {
            $_.MockServiceObject = $mockServiceObject

            InModuleScope -Parameter $_ -ModuleName 'SqlServerDsc' -ScriptBlock {
                Set-StrictMode -Version 1.0

                {
                    $MockServiceObject |
                        Assert-ManagedServiceType -ServiceType 'DatabaseEngine' -ErrorAction 'Stop'
                } | Should -Not -Throw
            }
        }
    }
}

File ConvertFrom-ManagedServiceType.Tests.ps1

BeforeAll {
    Import-Module -Name './SqlServerDsc.psm1' -Force
}

AfterAll {
    # Unload the module being tested so that it doesn't impact any other tests.
    Get-Module -Name 'SqlServerDsc' -All | Remove-Module -Force
}

Describe 'ConvertFrom-ManagedServiceType' -Tag 'Private' {
    Context 'When translating to a normalized service types' {
        BeforeDiscovery {
            $testCases = @(
                @{
                    MockServiceType  = 'SqlServer'
                    MockExpectedType = 'DatabaseEngine'
                }

                @{
                    MockServiceType  = 'SqlAgent'
                    MockExpectedType = 'SqlServerAgent'
                }

                @{
                    MockServiceType  = 'Search'
                    MockExpectedType = 'Search'
                }
            )
        }

        It 'Should properly map ''<MockServiceType>'' to normalized service type ''<MockExpectedType>''' -ForEach $testCases {
            InModuleScope -Parameters $_ -ModuleName 'SqlServerDsc' -ScriptBlock {
                Set-StrictMode -Version 1.0

                # Get the ManagedServiceType
                $managedServiceType = ConvertFrom-ManagedServiceType -ServiceType $MockServiceType

                $managedServiceType | Should -BeOfType [System.String]
                $managedServiceType | Should -Be $MockExpectedType
            }
        }
    }
}

File debug.ps1

$pesterConfig = New-PesterConfiguration -Hashtable @{
    CodeCoverage = @{
        Enabled = $true
        Path = './SqlServerDsc.psm1'
        OutputPath = './Pester_coverage.xml'
        UseBreakpoints = $true
    }
    Run = @{
        Path = @(
            # (non feasible) workaround 2 - Change the order the tests are run
            # Change order and the coverage become 100%
            '.\Assert-ManagedServiceType.Tests.ps1'
            '.\ConvertFrom-ManagedServiceType.Tests.ps1'
        )
    }
    Output = @{
        Verbosity = 'Detailed'
    }
}

Invoke-Pester -Configuration $pesterConfig

Describe your environment

Pester version     : 5.4.0 C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1                       
PowerShell version : 7.3.2
OS version         : Microsoft Windows NT 10.0.22623.0

Possible Solution?

Unknown :/

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions