Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes308downloads
WritingPesterTests.md397 linesDownload Raw Back to testing-guidelines
1# Writing Pester Tests2 3Note that this document does not replace the documents found in the [Pester](https://github.com/pester/pester) project.4This is just some quick tips and suggestions for creating Pester tests for this project.5The Pester community is vibrant and active, if you have questions about Pester or creating tests, the [Pester Wiki](https://github.com/pester/pester/wiki) has a lot of great information.6As of January 2018, PowerShell Core is using Pester version 4 which has some changes from earlier versions.7See [Migrating from Pester 3 to Pester 4](https://github.com/pester/Pester/wiki/Migrating-from-Pester-3-to-Pester-4) for more information.8 9When creating tests, keep the following in mind:10 11- Tests should not be overly complicated and test too many things.12    - Boil down your tests to their essence, test only what you need.13- Tests should be as simple as they can.14- Tests should generally not rely on any other test.15 16## Examples17 18Here's the simplest of tests:19 20```powershell21Describe "A variable can be assigned and retrieved" {22    It "Creates a variable and makes sure its value is correct" {23       $a = 124       $a | Should -Be 125   }26}27```28 29If you need to do type checking, that can be done as well:30 31```powershell32Describe "One is really one" {33    It "Compare 1 to 1" {34       $a = 135       $a | Should -Be 136    }37    It "1 is really an int" {38       $i = 139       $i | Should -BeOfType System.Int3240    }41}42```43 44If you are checking for proper errors, use the `Should -Throw -ErrorId` Pester syntax.45It checks against `FullyQualifiedErrorId` property, which is recommended because it does not change based on culture as an error message might.46 47```powershell48...49It "Get-Item on a nonexisting file should have error PathNotFound" {50    { Get-Item "ThisFileCannotPossiblyExist" -ErrorAction Stop } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand"51}52```53 54Note that if `Get-Item` were to succeed, the test will fail.55 56However, if you need to check the `InnerException` or other members of the ErrorRecord, you should use `-PassThru` parameter:57 58```powershell59It "InnerException sample" {60   $e = { Invoke-WebRequest https://expired.badssl.com/ } | Should -Throw -ErrorId "WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand" -PassThru61   $e.Exception.InnerException.NativeErrorCode | Should -Be 1217562}63```64 65## Describe/Context/It66 67For creation of PowerShell tests, the `Describe` block is the level of granularity suggested and one of three tags should be used: `CI`, `Feature`, or `Scenario`.68 69If the tag is not provided, the build process will fail.70 71### Describe72 73Creates a logical group of tests.74All `Mocks` and `TestDrive` contents defined within a `Describe` block are scoped to that `Describe`;75they will no longer be present when the `Describe` block exits.76A `Describe` block may contain any number of `Context` and `It` blocks.77 78### Context79 80Provides logical grouping of `It` blocks within a single `Describe` block. Any `Mocks` defined inside a `Context` are removed at the end of the `Context` scope, as are any files or folders added to the `TestDrive` during the `Context` block's execution. 81 82Any `BeforeEach` or `AfterEach` blocks defined inside a `Context` also only apply to tests within that `Context`.83 84### It85 86The `It` block is intended to be used inside of a `Describe` or `Context` block. If you are familiar with the AAA pattern (Arrange-Act-Assert), the body of the  `It` block is the appropriate location for an assert.87 88The convention is to assert a single expectation for each `It` block. The code inside of the `It` block should throw a terminating error if the expectation of the test is not met and thus cause the test to fail.89 90The name of the `It` block should expressively state the expectation of the test.91 92## Admin privileges in tests93 94Tests that require admin privileges **on Windows** must be additionally marked with `RequireAdminOnWindows` Pester tag.95 96In the Azure DevOps Windows CI, we run two different passes:97 98- The pass with exclusion of `RequireAdminOnWindows` tagged tests.99- The pass where only `RequireAdminOnWindows` tagged tests are being executed.100 101In each case, tests are executed with appropriate privileges.102 103Tests that need to be run with sudo **on Unix systems** must be additionally marked with `RequireSudoOnUnix` Pester tag.104 105`RequireSudoOnUnix` tag takes precedence over all other tags like `CI`, `Feature`, etc. (which are ignored when `RequireSudoOnUnix` is present).106Tests tagged with `RequireSudoOnUnix` will run as a separate pass for any Unix test.107 108## Selected Features109 110### Test Drive111 112A `PSDrive` is available for file activity during a test and this drive is limited to the scope of a single `Describe` block. The contents of the drive are cleared when a `Context` block is exited.113 114A test may need to work with file operations and validate certain types of file activities. It is usually desirable not to perform file activity tests that will produce side effects outside of an individual test.115 116Pester creates a `PSDrive` inside the user's temporary drive that is accessible via `TestDrive:` or `$TestDrive`. **Pester will remove** this drive after the test completes.117You may use this drive to isolate the file operations of your test to a temporary store.118 119The following example illustrates the feature:120 121```powershell122function Add-Footer($path, $footer) {123   Add-Content $path -Value $footer124}125 126Describe "Add-Footer" {127   $testPath="TestDrive:\test.txt"128   Set-Content $testPath -value "my test text."129   Add-Footer $testPath "-Footer"130   $result = Get-Content $testPath131 132   It "adds a footer" {133       (-join $result) | Should -BeExactly "my test text.-Footer"134   }135}136```137 138When this test completes, the contents of the `TestDrive:` will be removed.139 140### Parameter Generation141 142```powershell143$testCases = @(144    @{ a = 0; b = 1; ExpectedResult = 1 }145    @{ a = 1; b = 0; ExpectedResult = 1 }146    @{ a = 1; b = 1; ExpectedResult = 0 }147    @{ a = 0; b = 0; ExpectedResult = 0 }148    )149 150Describe "A test" {151    It "<a> -xor <b> should be <expectedresult>" -TestCases $testCases {152        param ($a, $b, $ExpectedResult)153        $a -xor $b | Should -Be $ExpectedResult154    }155}156```157 158### Mocking159 160Mocks the behavior of an existing command with an alternate implementation. This creates new behavior for any existing command within the scope of a `Describe` or `Context` block.161The function allows you to specify a script block that will become the command's new behavior.162 163The following example illustrates simple use:164 165```powershell166Context "Get-Random is not random" {167    Mock Get-Random { return 3 }168 169    It "Get-Random returns 3" {170        Get-Random | Should -Be 3171    }172}173```174 175More information may be found [here](https://github.com/pester/Pester/wiki/Mock).176 177### Free Code in a Describe block178 179Code execution in Pester can be very subtle and can cause issues when executing test code. The execution of code which lays outside of the usual code blocks may not happen as you expect. Consider the following:180 181```powershell182Describe it {183    Write-Host -For DarkRed "Before Context"184    Context "subsection" {185        Write-Host -for DarkRed "Before BeforeAll"186        BeforeAll { write-host -for Blue "In Context BeforeAll" }187        Write-Host -for DarkRed "After BeforeAll"188 189        Write-Host -for DarkRed "Before AfterAll"190        AfterAll { Write-Host -for Blue "In Context AfterAll" }191        Write-Host -for DarkRed "After AfterAll"192 193        BeforeEach { Write-Host -for Blue "In BeforeEach" }194        AfterEach { Write-Host -for Blue "In AfterEach" }195 196        Write-Host -for DarkRed "Before It"197        It "should not be a surprise" {198            1 | should -Be 1199        }200        Write-Host -for DarkRed "After It"201    }202    Write-Host -for DarkRed "After Context"203    Write-Host -for DarkGreen "Before Describe BeforeAll"204    BeforeAll { Write-Host -for DarkGreen "In Describe BeforeAll" }205    AfterAll { Write-Host -for DarkGreen "In Describe AfterAll" }206}207```208 209Now, when run, you can see the execution schedule:210 211```212PS# invoke-pester c:\temp\pester.demo.tests.ps1213Describing it214In Describe BeforeAll215Before Context216   Context subsection217In Context BeforeAll218Before BeforeAll219After BeforeAll220Before AfterAll221After AfterAll222Before It223In BeforeEach224    [+] should not be a surprise 79ms225In AfterEach226After It227In Context AfterAll228After Context229Before Describe BeforeAll230In Describe AfterAll231Tests completed in 79ms232Passed: 1 Failed: 0 Skipped: 0 Pending: 0233```234 235The `Describe` - `BeforeAll` block is executed before any other code even though it was at the bottom of the `Describe` block.236So if some state is set elsewhere in the `Describe` block, that state will not yet be visible (as the code will not yet been run).237 238Notice, too, that the `BeforeAll` block in `Context` is executed before any other code in that block.239Generally, you should have code reside in one of the code block elements of `BeforeAll`, `BeforeEach`, `AfterEach` and/or `AfterAll`, especially if those blocks rely on some state set by free code elsewhere in the block.240 241### Skipping Tests in Bulk242 243Sometimes it is beneficial to skip all the tests in a particular `Describe` block.244For example, tests which are not applicable to a platform could be skipped, and they would be reported as skipped.245 246The following is an example of how this may be done:247 248```powershell249Describe "Should not run these tests on non-Windows platforms" {250    BeforeAll {251        $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()252        if ( ! $IsWindows ) {253            $PSDefaultParameterValues["it:skip"] = $true254        }255    }256    AfterAll {257        $global:PSDefaultParameterValues = $originalDefaultParameterValues258    }259    Context "Block 1" {260        It "This block 1 test 1" {261            1 | should -Be 1262        }263        It "This is block 1 test 2" {264            1 | should -Be 1265        }266    }267    Context "Block 2" {268        It "This block 2 test 1" {269            2 | should -Be 1270        }271        It "This is block 2 test 2" {272            2 | should -Be 1273        }274    }275}276```277 278Here is the output when run on a Linux distribution:279 280```281Describing Should not run these tests on non-Windows platforms282   Context Block 1283    [!] This block 1 test 1 691ms284    [!] This is block 1 test 2 114ms285   Context Block 2286    [!] This block 2 test 1 73ms287    [!] This is block 2 test 2 6ms288```289 290and here is the output when run on a Windows distribution:291 292```293Describing Should not run these tests on non-Windows platforms294   Context Block 1295    [+] This block 1 test 1 86ms296    [+] This is block 1 test 2 33ms297   Context Block 2298    [-] This block 2 test 1 52ms299      Expected: {1}300      But was:  {2}301      22:             2 | should -Be 1302      at <ScriptBlock>, <No file>: line 22303    [-] This is block 2 test 2 77ms304      Expected: {1}305      But was:  {2}306      25:             2 | should -Be 1307      at <ScriptBlock>, <No file>: line 25308```309 310This technique uses the `$PSDefaultParameterValues` feature of PowerShell to temporarily set the `It` block parameter `-skip` to true (or in the case of Windows, it is not set at all)311 312### Multi-line strings313 314You may want to have a test like:315 316```powershell317It 'tests multi-line string' {318    Get-MultiLineString | Should -Be @'319first line320second line321'@322}323```324 325There are problems with using multi-line strings with verifying the output results.326The reason for it are line-ends.327 328They cause problems for two reasons:329 330- They are different on different platforms (`\r\n` on Windows and `\n` on Unix).331- Even on the same system, they depend on the way how the repo was cloned (local git configuration).332 333Particularly, in the default Azure DevOps CI Windows image, you will get `\n` line ends in all your files.334That causes problems, because at runtime `Get-MultiLineString` would likely produce `\r\n` line ends on Windows.335 336Some workaround could be added, but they are sub-optimal and make reading test code harder.337 338```powershell339function normalizeEnds([string]$text)340{341    $text -replace "`r`n?|`n", "`r`n"342}343 344It 'tests multi-line string' {345    normalizeEnds (Get-MultiLineString) | Should -Be (normalizeEnds @'346first line347second line348'@)349}350```351 352When appropriate, you can avoid creating multi-line strings at the first place.353These commands create an array of strings:354 355- `Get-Content`356- `Out-String -Stream`357 358## Pester Do and Don't359 360### Do361 3621. Name your file `<descriptive_test_name>.tests.ps1`.3632. Keep tests simple:364    - Test only what you need.365    - Reduce dependencies.3663. Be sure to tag your `Describe` blocks based on their purpose:367    - Tag `CI` indicates that it will be run as part of the continuous integration process. These should be unit test like, and generally take less than a second.368    - Tag `Feature` indicates a higher level feature test (we will run these on a regular basis), for example, tests which go to remote resources, or test broader functionality.369    - Tag `Scenario` indicates tests of integration with other features (these will be run on a less regular basis and test even broader functionality than feature tests.3704. Make sure that `Describe`/`Context`/`It` descriptions are useful.371    - The error message should not be the place where you describe the test.3725. Use `Context` to group tests.373    - Multiple `Context` blocks can help you group your test suite into logical sections.3746. Use `BeforeAll`/`BeforeEach`/`AfterEach`/`AfterAll` instead of custom initiators.3757. Use `Should -Throw -ErrorId` to check for expected errors.3768. Use `-TestCases` when iterating over multiple `It` blocks.3779. Use code coverage functionality where appropriate.37810. Use `Mock` functionality when you don't have your entire environment.37911. Avoid free code in a `Describe` block.380    - Use `BeforeAll`/`BeforeEach`/`AfterEach`/`AfterAll`.381    - See [Free Code in a Describe block](WritingPesterTests.md#free-code-in-a-describe-block)38212. Avoid creating or using test files outside of `TESTDRIVE:`.383    - `TESTDRIVE:` has automatic clean-up.38413. Keep in mind that we are creating cross platform tests.385    - Avoid using the registry.386    - Avoid using COM.38714. Avoid being too specific about the _count_ of a resource as these can change platform to platform.388    - Example: Avoid checking for the count of loaded format files, but rather check for format data for a specific type.389 390### Don't391 3921. Don't have too many evaluations in a single `It` block.393   - The first `Should` failure will stop that block.3942. Don't use `Should` outside of an `It` Block.3953. Don't use the word "Error" or "Fail" to test a positive case.396   - Example: Rephrase the negative sentence `"Get-ChildItem TESTDRIVE: shouldn't fail"` to the following positive case `"Get-ChildItem should be able to retrieve file listing from TESTDRIVE"`.397