13
Unit Testing PowerShell Matt Wrock (@mwrockx) April 22 – 24 Microsoft campus Redmond

Unit testing powershell

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Unit testing powershell

Unit Testing PowerShellMatt Wrock (@mwrockx)

April 22 – 24Microsoft campusRedmond

Page 2: Unit testing powershell

Describe "Invoke-Reboot" { Context "When reboots are suppressed" { Mock New-Item -parameterFilter {$Path -like "*\Boxstarter*"} Mock Restart $Boxstarter.RebootOk=$false $Boxstarter.IsRebooting=$false Invoke-Reboot

it "will not create Restart file" { Assert-MockCalled New-Item -times 0 } it "will not restart" { Assert-MockCalled Restart -times 0 } it "will not toggle reboot" { $Boxstarter.IsRebooting | should be $false } } }

A PowerShell Unit Test in the wild

I’m all about test coverage

Executing all tests in C:\dev\boxstarter\tests\Invoke-Reboot.tests.ps1Describing Invoke-Reboot When reboots are suppressed[+] will not create Restart file 6ms[+] will not restart 11ms[+] will not toggle reboot 4ms When reboots are not suppressed[+] will create Restart file 11ms[+] will restart 5ms[+] will toggle reboot 2msTests completed in 41msPassed: 6 Failed: 0

Page 3: Unit testing powershell

Why Test PowerShell?

Do NOT UnitTest your scripts if you like surprises!

• Your scripts will likely be easier to understand

• Catch regressions• How does your API really

feel?

Page 4: Unit testing powershell

Managed Unit Testing Tools vs. PowerShellManaged• Xunit - https://xunit.codeplex.com/

• Nunit - http://www.nunit.org/

• MSTest

PowerShell• Pester - https://github.com/pester/Pester

• PSUnit - http://psunit.org/

• PSTest - https://github.com/knutkj/pstest/wiki

If practical, test powershell code in PowerShell:

• Saves the overhead of starting a runspace for each test• Environment more closely resembles the user’s

Page 5: Unit testing powershell

PowerShell Unit Testing Patterns

Do not use these patterns in PowerShell. They are much too

twirly.

Page 6: Unit testing powershell

Abstracting the untestableFunction Under Test function Install-ChocolateyVsixPackage { $installer = Join-Path $env:VS110COMNTOOLS "..\IDE\VsixInstaller.exe" $download="MyVSIX.vsix" Write-Debug "Installing VSIX using $installer" $exitCode = Install-Vsix "$installer" "$download" if($exitCode -gt 0 -and $exitCode -ne 1001) { #1001: Already installed Write-ChocolateyFailure "There was an error installing." return } Write-ChocolateySuccess} Wrap the EXEfunction Install-Vsix($installer, $installFile) { Write-Host "Installing $installFile using $installer" $psi = New-Object System.Diagnostics.ProcessStartInfo $psi.FileName=$installer $psi.Arguments="/q $installFile" $s = [System.Diagnostics.Process]::Start($psi) $s.WaitForExit() return $s.ExitCode}

Test and Mock the WrapperContext "When VSIX is already installed" { Mock Install-Vsix {return 1001}

Install-ChocolateyVsixPackage

It "should succeed" { Assert-MockCalled Write-ChocolateySuccess } }

Page 7: Unit testing powershell

Mocking Cmdlets function Get-LatestVSVersion { $versions=( get-ChildItem HKLM:SOFTWARE\Wow6432Node\Microsoft\VisualStudio ` -ErrorAction SilentlyContinue | ? { ($_.PSChildName -match "^[0-9\.]+$") } | ? { $_.property -contains "InstallDir" } | sort {[int]($_.PSChildName)} -descending ) if($versions -and $versions.Length){ $version = $versions[0] }elseif($versions){ $version = $versions } return $version}

Context "When version 9, 10 and 11 is installed" { Mock Get-ChildItem {@( @{PSChildName="9.0";Property=@("InstallDir");PSPath="9"}, @{PSChildName="10.0";Property=@("InstallDir");PSPath="10"}, @{PSChildName="11.0";Property=@("InstallDir");PSPath="11"} )} ` -parameterFilter { $path -eq "HKLM:SOFTWARE\Wow6432Node\Microsoft\VisualStudio" } Mock get-itemproperty {@{InstallDir=$Path}}

$result=Get-LatestVSVersion It "should return version 11" { $result | Should Be 11 } }

Page 8: Unit testing powershell

Isolating file operations function Set-BoxstarterShare { param( [string]$shareName="Boxstarter", [string[]]$accounts=@("Everyone") )

foreach($account in $accounts){ $acctOption += "/GRANT:'$account,READ' " } IEX "net share $shareName='$($Boxstarter.BaseDir)' $acctOption" if($LastExitCode -ne 0) { Throw "Share was not succesfull." }}

Describe "Set-BoxstarterShare" { $testRoot=(Get-PSDrive TestDrive).Root

Context "When setting share with no parameters" { MkDir "$testRoot\boxstarter" | Out-Null $Boxstarter.BaseDir="$testRoot\Boxstarter"

Set-BoxstarterShare

It "Should create Boxstarter Share"{ Test-Path "\\$env:Computername\Boxstarter" | should be $true } It "Should give read access to everyone"{ (net share Boxstarter) | ? { $_.StartsWith("Permission")} | % { $_.ToLower().EndsWith("everyone, read") | Should be $true } } net share Boxstarter /delete } }

Using the Pester TestDrive:\

Page 9: Unit testing powershell

Testing Exceptions function Set-BoxstarterShare { param( [string]$shareName="Boxstarter", [string[]]$accounts=@("Everyone") )

foreach($account in $accounts){ $acctOption += "/GRANT:'$account,READ' " } IEX "net share $shareName='$($Boxstarter.BaseDir)' $acctOption" if($LastExitCode -ne 0) { Throw "Share was not succesfull." }}

Context "When share already exists" { MkDir "$testRoot\boxstarter" | Out-Null $Boxstarter.BaseDir="$testRoot\Boxstarter" Net share Boxstarter="$($Boxstarter.BaseDir)" try {Set-BoxstarterShare} catch{$ex=$_}

It "Should throw exception"{ $ex | should not be $null } net share Boxstarter /delete }

Page 10: Unit testing powershell

Debugging TestsDoug Finke’s IsePesterhttps://github.com/dfinke/IsePester

Chocolatey (downloads pester, isepester and imports them in your ISE profile):

cinst IsePesterCtrl+F5 Debugs tests in the active editor

Page 11: Unit testing powershell

A PowerShell CI Build<Project ToolsVersion="4.0“ DefaultTargets="Go“ xmlns="http://schemas.microsoft.com/developer/msbuild/2003">    <PropertyGroup>      <GoDependsOn>Tests</GoDependsOn>      <Configuration>Release</Configuration>      <Platform>Any CPU</Platform>    </PropertyGroup>   

<Target Name="Go" DependsOnTargets="$(GoDependsOn)" />   

<Target Name="Tests">      <Exec Command="cmd /c $(MSBuildProjectDirectory)\pester\bin\pester.bat" />    </Target> </Project>

Simple MSBuild script

A better way: Psakehttps://github.com/psake/psake $psake.use_exit_on_error = $trueproperties { $baseDir = (Split-Path -parent $psake.build_script_dir)}

Task default -depends Test

Task Test { pushd "$baseDir" $pesterDir = (dir $env:ChocolateyInstall\lib\Pester*) if($pesterDir.length -gt 0) {$pesterDir = $pesterDir[-1]} exec {."$pesterDir\tools\bin\Pester.bat" $baseDir/Tests } popd}

Page 12: Unit testing powershell

A PowerShell CI BuildAdding test detail to TeamCity builds: https://github.com/pester/Pester/wiki/Showing-Test-Results-in-TeamCity

Page 13: Unit testing powershell

PowerShell Unit Test Samples

• Chocolateyhttps://github.com/chocolatey/chocolatey/tree/master/tests• Pester

https://github.com/pester/Pester/tree/master/Functions• Boxstarter

http://boxstarter.codeplex.com/