Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Beyond Pester 103 : Applying a testing mindset

Glenn
April 22, 2023

Beyond Pester 103 : Applying a testing mindset

Pester is a great tool. You’ve probably used it for unit and acceptance testing, but you feel there’s something missing; How much more is there to testing? Is it just making sure my script works? It’s time to look at how testing can be used in everything (well almost everything…) you do!

Glenn

April 22, 2023
Tweet

More Decks by Glenn

Other Decks in Technology

Transcript

  1. Beyond Pester 103 @glennsarti glenn-sarti Applying a testing mindset to

    PowerShell Glenn Sarti - Senior Software Engineer at
  2. @glennsarti Cynefin Framework “The Cynefin framework (/ˈkʌnɪvɪn/ KUN-iv-in) is a

    conceptual framework used to aid decision-making” - https://www.youtube.com/watch?v=N7oz366X0-8
  3. Designing automated test suites About 60% automation testing and 40%

    manual Contribute towards maintaining regression suite Ability to write clear and concise test/bug reports Review and analyze system specifications Regression testing, reporting @glennsarti
  4. Designing automated test suites About 60% automation testing and 40%

    manual Contribute towards maintaining regression suite Ability to write clear and concise test/bug reports Review and analyze system specifications Regression testing, reporting @glennsarti
  5. @glennsarti Testing is one of the most profitable investments engineers

    can make to improve the reliability of their product. Testing isn’t an activity that happens once or twice in the lifecycle of a project; it’s continuous. ” “ - https://sre.google/sre-book/testing-reliability/
  6. @glennsarti “Shifting left” is about building quality into the software

    development process. When you shift left, fewer things break in production, because any issues are detected and resolved earlier. - Puppet State of DevOps Report 2016
  7. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  8. @glennsarti @glennsarti Param ( $ScriptPath = "C:\Automation\AD\Cleanup\", $LogDirectory = "logfiles\",

    $Date = (Get-Date).ToString("dd-MM-yyyy"), $NamedExceptions = (Get-Content $ScriptPath`ADnamedExceptions.txt), $Server = "DC002.domain.com" ) Function Get-AgedAccounts { Param ( [string]$AccountType, [int]$AgedAccountThreshold, [int]$NewAccountThreshold ) $LastLogonDate = (Get-Date).AddDays(-$AgedAccountThreshold) $WhenCreated = (Get-Date).AddDays(-$NewAccountThreshold) If ($AccountType -eq "User") { $AgedAccounts = Get-ADUser -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -lt $WhenCreated -and samAccountType -eq "805306368") -and ((LastLogonDate -lt $LastLogonDate) -or (LastLogonDate -notlike "*")) } -Properties lastLogonDate,whenCreated,passWordLastSet,whenChanged -Server $server | Select-Object -Properties distinguishedName,samAccountName,lastLogonDate,whenCreated,passWordLastSet,whenChanged,objectClass $AgedAccounts } ElseIf ($AccountType -eq "Computer") { $AgedAccounts = Get-ADComputer -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -lt $WhenCreated -and samAccountType -eq "805306369") -and ((LastLogonDate -lt $LastLogonDate) -or (LastLogonDate -notlike "*")) } -Properties lastLogonDate,whenCreated,passWordLastSet,whenChanged -Server $server | Select-Object -Property distinguishedName,samAccountName,lastLogonDate,whenCreated,passWordLastSet,whenChanged,objectClass $AgedAccounts } Else { $AgedAccounts } } Function Get-DisabledAccounts { Param ( [string]$AccountType, [int]$LastModifiedThreshold ) $LastModifiedDate = (Get-Date).AddDays(-$LastModifiedThreshold) If ($AccountType -eq "User") { $DisabledAccounts = Get-ADUser -Filter { enabled -eq $False -and samAccountType -eq "805306368" -and whenChanged -lt $LastModifiedDate } -properties lastLogonDate,whenCreated,passWordLastSet,whenChanged -server $server | Select-Object -Property distinguishedName,samAccountName,lastLogonDate,whenCreated,passWordLastSet,whenChanged,objectClass $DisabledAccounts } ElseIf ($AccountType -eq "Computer") { $DisabledAccounts = Get-ADComputer -Filter { enabled -eq $False -and samAccountType -eq "805306369" -and whenChanged -lt $LastModifiedDate } -properties lastLogonDate,whenCreated,passWordLastSet,whenChanged -server $server | Select-Object -Property distinguishedName,samAccountName,lastLogonDate,whenCreated,passWordLastSet,whenChanged,objectClass $DisabledAccounts } Else { $DisabledAccounts } } $AgedAccounts = $null $AgedAccounts = Get-AgedAccounts User 45 21 $AgedAccounts += Get-AgedAccounts Computer 45 14 $AgedAccounts.Count $DisabledAccounts = $null $DisabledAccounts = Get-DisabledAccounts Computer 183 # $DisabledAccounts += Get-DisabledAccounts User 183 $DisabledAccounts.Count ForEach ($AgedAccount in $AgedAccounts) { If ($NamedExceptions -contains $AgedAccount.DistinguishedName) { $Result = "Match found in named exceptions file" } Else { Disable-ADAccount $AgedAccount.DistinguishedName -Server $Server # -WhatIf $Result = $? } Add-Member -InputObject $AgedAccount -MemberType NoteProperty ` -Name ScriptDisabled -Value $Result } $LogFile = $Date + "disabled accounts.csv" $AgedAccounts | Export-Csv $ScriptPath$LogDirectory$LogFile -NoTypeInformation ForEach ($DisabledAccount in $DisabledAccounts) { If ($NamedExceptions -contains $DisabledAccount.DistinguishedName) { $Result = "Match found in named exceptions file" } Else { Remove-ADObject $DisabledAccount.DistinguishedName -Server $Server -Confirm:$False -Recursive # -WhatIf $Result = $? } Add-Member -InputObject $DisabledAccount -MemberType NoteProperty -Name ScriptDeleted -Value $Result } $LogFile = $Date + "-deleted accounts.csv" $DisabledAccounts | Export-Csv $ScriptPath$LogDirectory$LogFile -NoTypeInformation
  9. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  10. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  11. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  12. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  13. @glennsarti • Find accounts that haven’t logged in for 45

    days • Exclude some accounts • Disable the accounts • Log the results to a file @glennsarti
  14. @glennsarti @glennsarti Describe "Get-OldAccounts" { BeforeEach { $OldUserName1 = "Jasper"

    $OldUserName2 = "Tomato" $NotStaleUserName = "Pinapple" Function New-MockUser($Name, $Age) { # Insert mocking code here } New-MockUser $OldUserName1 46 New-MockUser $OldUserName2 100 New-MockUser $NotStaleUserName 45 } It "only returns old accounts" { $Actual = Get-OldAccounts -OlderThanDays 45 $Actual | Should-Be @($OldUserName1, $OldUserName2) } }
  15. @glennsarti @glennsarti Describe "Get-OldAccounts" { BeforeEach { $OldUserName1 = "Jasper"

    $OldUserName2 = "Tomato" $NotStaleUserName = "Pinapple" Function New-MockUser($Name, $Age) { # Insert mocking code here } New-MockUser $OldUserName1 46 New-MockUser $OldUserName2 100 New-MockUser $NotStaleUserName 45 } It "only returns old accounts" { $Actual = Get-OldAccounts -OlderThanDays 45 $Actual | Should-Be @($OldUserName1, $OldUserName2) } } Old accounts Not old account
  16. @glennsarti @glennsarti Describe "Get-OldAccounts" { BeforeEach { $OldUserName1 = "Jasper"

    $OldUserName2 = "Tomato" $NotStaleUserName = "Pinapple" Function New-MockUser($Name, $Age) { # Insert mocking code here } New-MockUser $OldUserName1 46 New-MockUser $OldUserName2 100 New-MockUser $NotStaleUserName 45 } It "only returns old accounts" { $Actual = Get-OldAccounts -OlderThanDays 45 $Actual | Should-Be @($OldUserName1, $OldUserName2) } }
  17. @glennsarti @glennsarti Get-OldAccount ` -OlderThanDays 45 | Skip-ExcludedAccounts ` -Exclude

    $IgnoreAccounts | Disable-Account | Out-AuditFile -Path $AuditFileCSV $AgedAccounts = $null $AgedAccounts = Get-AgedAccounts User 45 # ... ForEach ($AgedAccount in $AgedAccounts) { If ($NamedExceptions -contains $AgedAccount.DistinguishedName) { $Result = "Match found in named exceptions file" } Else { Disable-ADAccount $AgedAccount.DistinguishedName ` -Server $Server $Result = $? } Add-Member -InputObject $AgedAccount -MemberType NoteProperty ` -Name ScriptDisabled -Value $Result } $LogFile = $Date + "disabled accounts.csv" $AgedAccounts | Export-Csv $ScriptPath$LogDirectory$LogFile -NoTypeInformation Before After
  18. @glennsarti • Find accounts that were disabled 6 months ago

    • Exclude some accounts • Delete the accounts • Log the results to a file @glennsarti
  19. @glennsarti @glennsarti Get-OldAccount -OlderThanDays 45 | Skip-ExcludedAccounts -Exclude $IgnoreAccounts |

    Disable-Account | Out-AuditFile -Path $AuditFileCSV Get-DisabledAccount -OlderThanDays 180 | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Remove-Account | Out-AuditFile -Path $AuditFileCSV
  20. @glennsarti @glennsarti Function Get-AgedAccounts { Param ( [string]$AccountType, [int]$AgedAccountThreshold, [int]$NewAccountThreshold

    ) $LastLogonDate = (Get-Date).AddDays(-$AgedAccountThreshold) $WhenCreated = (Get-Date).AddDays(-$NewAccountThreshold) If ($AccountType -eq "User") { $AgedAccounts = Get-ADUser -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -lt $WhenCreated –and …
  21. @glennsarti @glennsarti Function Get-AgedAccounts { Param ( [string]$AccountType, [int]$AgedAccountThreshold, [int]$NewAccountThreshold

    ) $LastLogonDate = (Get-Date).AddDays(-$AgedAccountThreshold) $WhenCreated = (Get-Date).AddDays(-$NewAccountThreshold) If ($AccountType -eq "User") { $AgedAccounts = Get-ADUser -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -lt $WhenCreated –and …
  22. @glennsarti @glennsarti Function Get-AgedAccounts { Param ( [string]$AccountType, [int]$AgedAccountThreshold, [int]$NewAccountThreshold

    ) $LastLogonDate = (Get-Date).AddDays(-$AgedAccountThreshold) $WhenCreated = (Get-Date).AddDays(-$NewAccountThreshold) If ($AccountType -eq "User") { $AgedAccounts = Get-ADUser -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -lt $WhenCreated –and …
  23. @glennsarti @glennsarti Function Get-AgedAccounts { Param ( [string]$AccountType, [int]$AgedAccountThreshold, [DateTime]$CreatedAfter

    ) $LastLogonDate = (Get-Date).AddDays(-$AgedAccountThreshold) If ($AccountType -eq "User") { $AgedAccounts = Get-ADUser -Filter { (enabled -eq $True -and PasswordNeverExpires -eq $False -and WhenCreated -ge $CreatedAfter –and …
  24. @glennsarti @glennsarti Get-OldAccount -OlderThanDays 45 | Skip-ExcludedAccounts -Exclude $IgnoreAccounts |

    Disable-Account | Out-AuditFile -Path $AuditFileCSV Get-DisabledAccount -OlderThanDays 180 | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Remove-Account | Out-AuditFile -Path $AuditFileCSV
  25. @glennsarti @glennsarti $DisableTime = (Get-Date).AddDays(-45) $RemoveTime = (Get-Date).AddMonths(-6) Get-OldAccount -OlderThan

    $DisableTime | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Disable-Account | Out-AuditFile -Path $AuditFileCSV Get-DisabledAccount -OlderThan $RemoveTime | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Remove-Account | Out-AuditFile -Path $AuditFileCSV
  26. @glennsarti Types of tests – Unit (Black box) “Tools do

    one thing … Tools don’t know where their input data has generated … Tools accept all input only from their parameters … Tools don’t know how their output will be used, “ - The Many Forms of Scripting: which to use - Manning @glennsarti
  27. @glennsarti @glennsarti …here’s another one: TDD (Test-driven development) … why

    not write down what your trying to accomplish in the form of tests … Make sure all of those tests fail and then write PowerShell code to accomplish the necessary tasks until all of the tests pass. - https://mikefrobbins.com/2014/10/09/using-pester-for-test-driven-development-in-powershell/
  28. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development
  29. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass 5. Refactor 6. Repeat @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development
  30. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass 5. Refactor 6. Repeat @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development Iterate
  31. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass 5. Refactor 6. Repeat @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development Always adding quality
  32. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass 5. Refactor 6. Repeat @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development Focus on requirements
  33. @glennsarti 1. Add a test 2. It should fail 3.

    Write the code 4. Tests should pass 5. Refactor 6. Repeat @glennsarti - https://en.wikipedia.org/wiki/Test-driven_development Small QoL things
  34. @glennsarti @glennsarti … we are 100% capable of doing TDD

    with Powershell, No Excuses! … It’s that simple, and it’s a beautiful thing. - https://devingleasonlambert.medium.com/test-driven-development-by-example-using-powershell-what-is-tdd-9a5006c9931 - Devin Gleason-Lambert
  35. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  36. @glennsarti Describe "Get-OldAccounts" { BeforeEach { $OldUserName1 = "Jasper" $OldUserName2

    = "Tomato" $NotStaleUserName = "Pinapple" Function New-MockUser($Name, $Age) { # Insert mocking code here } New-MockUser $OldUserName1 46 New-MockUser $OldUserName2 100 New-MockUser $NotStaleUserName 45 } It "only returns old accounts" { $Actual = Get-OldAccounts -OlderThanDays 45 $Actual | Should-Be @($OldUserName1, $OldUserName2) } }
  37. @glennsarti $DisableTime = (Get-Date).AddDays(-45) $RemoveTime = (Get-Date).AddMonths(-6) Get-OldAccount -OlderThan $DisableTime

    | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Disable-Account | Out-AuditFile -Path $AuditFileCSV Get-DisabledAccount -OlderThan $RemoveTime | Skip-ExcludedAccounts -Exclude $IgnoreAccounts | Remove-Account | Out-AuditFile -Path $AuditFileCSV
  38. @glennsarti @glennsarti AD Account Cleanup Script • Disable accounts that

    haven’t logged in for 45 days • Delete disabled accounts after 6 months • Be able to exclude some accounts • Log all activities
  39. @glennsarti TDD for everyone @glennsarti “The code is more what

    you’d call ‘guidelines’ than actual rules.” - Captain Barbosa – Pirates of the Caribbean
  40. @glennsarti A software tester walks into a bar ... Orders

    a beer Orders ten beers Orders a nothing Orders 200 beers Tries to leave without paying Orders -1 beers Orders a cat @glennsarti
  41. @glennsarti @glennsarti Happy Sad Weird A software tester walks into

    a bar ... Orders a beer Orders ten beers Orders a nothing Orders 200 beers Tries to leave without paying Orders -1 beers Orders a cat
  42. @glennsarti AD Cleanup script – Get old Accounts An account

    logged in 46 days ago An account logged in 45 days ago @glennsarti Happy
  43. @glennsarti AD Cleanup script – Get old Accounts An account

    logged in 46 days ago An account logged in 45 days ago An account that’s never logged in Network error @glennsarti Happy Sad
  44. @glennsarti AD Cleanup script – Get old Accounts An account

    logged in 46 days ago An account logged in 45 days ago An account that’s never logged in Network error An account logged in 1 day into the future @glennsarti Happy Sad Weird
  45. @glennsarti AD Cleanup script – Get old Accounts An account

    logged in 46 days ago An account logged in 45 days ago An account that’s never logged in Network error An account logged in 1 day into the future @glennsarti Happy Sad Weird
  46. @glennsarti @glennsarti “ If it hurts, do it more frequently,

    and bring the pain forward. “ - https://www.amazon.com.au/Continuous-Delivery-Reliable-Deployment-Automation/dp/0321601912 - Jez Humble
  47. @glennsarti Operation Validation Framework - https://github.com/PowerShell/Operation-Validation-Framework A set of tools

    for executing validation of the operation of a system. It provides a way to organize and execute Pester tests which are written to validate operation … ” “ @glennsarti
  48. @glennsarti @glennsarti Feature: It retrieves old accounts Background: Login to

    Test AD Given test AD server 'testad.local' And a user '[email protected]' which logged in 45 days ago And a user '[email protected]' which logged in 46 days ago And a user '[email protected]' which logged in 100 days ago Scenario: Getting many accounts When getting accounts older than 45 days Then it returns user '[email protected]' And it returns user '[email protected]' And it does not return user '[email protected]'
  49. @glennsarti @glennsarti HashiCorp - Sentinel deny_unencrypted_managed_disk = rule when {

    all allManagedDisks as _, disks { all disks.change.after.encryption_settings as encryption_settings { encryption_settings.enabled is true } } }
  50. @glennsarti @glennsarti package gatlingBlog import scala.concurrent.duration._ import io.gatling.core.Predef._ import io.gatling.http.Predef._

    class SartiDevSimulation1 extends Simulation { val httpProtocol = http .baseUrl("https://sarti.dev") val scn = scenario("SendSimpleQuery") .exec( http("root_request") .get("/") ) setUp(scn.inject( atOnceUsers(100) ).protocols(httpProtocol)) }
  51. @glennsarti @glennsarti package gatlingBlog import scala.concurrent.duration._ import io.gatling.core.Predef._ import io.gatling.http.Predef._

    class SartiDevSimulation1 extends Simulation { val httpProtocol = http .baseUrl("https://sarti.dev") val scn = scenario("SendSimpleQuery") .exec( http("root_request") .get("/") ) setUp(scn.inject( atOnceUsers(100) ).protocols(httpProtocol)) } Request URL Load
  52. … If it's truth you're interested in, Dr.Tyree's Philosophy class

    is right down the hall — Indiana Jones @glennsarti
  53. Does my PowerShell do what it should? How do I

    know my PowerShell does what it should? @glennsarti
  54. THANK YOU! Please use the link below to submit a

    session rating! @glennsarti http://sarti.dev/ https://speakerdeck.com/glennsarti https://powershellsummit.org/sessionfeedback
  55. @glennsarti Resources Beyond Pester https://sarti.dev/presentation/powershell-summit2018-pester https://sarti.dev/presentation/powershell-summit2019-pester DevOps https://www.amazon.com.au/Phoenix-Project-DevOps-Helping-Business/dp/0988262592 https://sre.google/sre-book/testing-reliability/ https://services.google.com/fh/files/misc/state-of-devops-2018.pdf

    https://www.amazon.com.au/Continuous-Delivery-Reliable-Deployment-Automation/dp/0321601912 TDD https://en.wikipedia.org/wiki/Test-driven_development https://mikefrobbins.com/2014/10/09/using-pester-for-test-driven-development-in-powershell/ https://sqldbawithabeard.com/2017/06/30/creating-a-powershell-module-and-tdd-for-get-sqldiagrecommendations/ https://devingleasonlambert.medium.com/test-driven-development-by-example-using-powershell-what-is-tdd-9a5006c9931 Images https://unsplash.com @glennsarti