DBTrenches / SQLChecks

Helper functions and tests for SQL Server
MIT License
3 stars 2 forks source link

Upgrade to Pester v5 #21

Open taddison opened 3 years ago

taddison commented 3 years ago

With enhancements to v5 (see https://pester-docs.netlify.app/docs/usage/data-driven-tests) it should be possible to migrate.

Roughly, this would entail:

There may also be some advantages to this approach that can be realized by having the list of e.g. $databasesToCheck persist without requiring a cache (as discovery runs once).

taddison commented 3 years ago

Here is an example of creating dynamic It blocks based on a file-level set of databases, in addition to a per-test exclusion set. In cases where you don't need to perform additional checks you can use the file-level variable directly.

BeforeDiscovery {
  $databases = @("One", "Two", "Skip")
}

Describe "Iterate the array of databases" {
  BeforeDiscovery {
    $databasesToCheck = $databases | Where-Object { $_ -ne "Skip" }
  }

  It "<_> should exist" -ForEach $databasesToCheck {
    $database = $_
    $database | Should -Not -Be "SKip"
  }
}
taddison commented 3 years ago

And here is an example of injecting external context (e.g. $config).

param (
  [Parameter(Mandatory)]
  [string] $DatabaseToSkip
)

BeforeDiscovery {
  $databases = @("One", "Two", "Skip")
}

Describe "Iterate the array of databases" {
  BeforeDiscovery {
    $databasesToCheck = $databases | Where-Object { $_ -ne $DatabaseToSkip }
  }

  It "<_> should exist" -ForEach $databasesToCheck {
    $database = $_
    $database | Should -Not -Be "Skip"
  }
}

Invoked via:

$container = New-PesterContainer -Path "*.*" -Data @{ DatabaseToSkip = "Skip" }
Invoke-Pester -Container $container -Output Detailed
taddison commented 3 years ago

To allow a describe block to only execute if the config option is present, we can use the -ForEach argument there too. I've created a wrapper function SqlChecksDescribe which allows the test to be written more tersely.

I believe this will allow invocation of a whole config file, and rather than needing to loop through each config value and Invoke-Pester -Tag .. that can all be pushed into the test itself.

I'd like to skip passing $checks if possible, and maybe even come up with a convention based way to extract the tag from the test - if each describe had to contain the Tag to start, e.g. TagName - TagDescription, then the Tag could be extracted from the description and the invocation would be even terser:

SqlChecksDescribe "SSBEnabled - Service Broker Enabled" $checks { ... }

Maybe a factory function that accepts $checks and could then be invoked once per-file? Not sure on exactly how discovery works and if that would be possible (and rules around conditional function definitions in powershell).

param (
  [Parameter(Mandatory)]
  [string] $DatabaseToSkip
)

BeforeDiscovery {
  $databases = @("One", "Two", "Skip")
  $checks = @("A", "B")
}

Function SqlChecksDescribe {
  [CmdletBinding()]

  Param(
    [Parameter(Mandatory, Position = 0)]
    $Name,
    [Parameter(Mandatory, Position = 1)]
    $Tag,
    [Parameter(Mandatory, Position = 2)]
    $Checks,
    [Parameter(Mandatory, Position = 3)]
    $Fixture
  )
  Describe -Name $name -Tag $Tag -ForEach @($Checks | Where-Object { $_ -eq $Tag }) -Fixture $Fixture
}

Describe "Iterate the array of databases" {
  BeforeDiscovery {
    $databasesToCheck = $databases | Where-Object { $_ -ne $DatabaseToSkip }
  }

  It "<_> should exist" -ForEach $databasesToCheck {
    $database = $_
    $database | Should -Not -Be "Skip"
  }
}

Describe "A description" -Tag "A" -ForEach @($checks | Where-Object { $_ -eq "A" }) {
  It "should run" {
    $true | Should -Be $true
  }
}

SqlChecksDescribe "B Description" "B" $checks {
  It "should run" {
    $true | Should -Be $true
  }
}

SqlChecksDescribe "C Description" "C" $checks {
  It "should not run" {
    $true | Should -Be $false
  }
}
taddison commented 3 years ago

Turns out it's fairly trivial to do what I've suggested above - each test file needs to contain a wrapper function SqlChecksDescribe that captures the value of $checks and passes everything on to the shared function SqlChecksDescribeShared.

param (
  [Parameter(Mandatory)]
  [string] $DatabaseToSkip
)

BeforeDiscovery {
  $databases = @("One", "Two", "Skip")
  $checks = @("A", "B")

  Function SqlChecksDescribe {
    [CmdletBinding()]
    Param(
      [Parameter(Mandatory, Position = 0)]
      $Name,
      [Parameter(Mandatory, Position = 1)]
      $Fixture
    )

    SqlChecksDescribeShared -Name $name -Checks $checks -Fixture $Fixture
  }
}

Function SqlChecksDescribeShared {
  [CmdletBinding()]

  Param(
    [Parameter(Mandatory, Position = 0)]
    $Name,
    [Parameter(Mandatory, Position = 1)]
    $Checks,
    [Parameter(Mandatory, Position = 2)]
    $Fixture
  )

  $firstSpace = $Name.IndexOf(" ")
  if ($firstSpace -eq -1) {
    $Tag = $Name
  }
  else {
    $Tag = $Name.Substring(0, $firstSpace)
  }

  Describe -Name $name -Tag $Tag -ForEach @($Checks | Where-Object { $_ -eq $Tag }) -Fixture $Fixture
}

Describe "Iterate the array of databases" {
  BeforeDiscovery {
    $databasesToCheck = $databases | Where-Object { $_ -ne $DatabaseToSkip }
  }

  It "<_> should exist" -ForEach $databasesToCheck {
    $database = $_
    $database | Should -Not -Be "Skip"
  }
}

Describe "A description" -Tag "A" -ForEach @($checks | Where-Object { $_ -eq "A" }) {
  It "should run" {
    $true | Should -Be $true
  }
}

SqlChecksDescribe "B Description" {
  It "should run" {
    $true | Should -Be $true
  }
}

SqlChecksDescribe "C Description" {
  It "should not run" {
    $true | Should -Be $false
  }
}