|
| 1 | +# Specific instructions for the PowerShell module project SqlServerDsc |
| 2 | + |
| 3 | +Assume that the word "command" references to a public command, and the word |
| 4 | +"function" references to a private function. |
| 5 | + |
| 6 | +PowerShell commands that should be public should always have its separate |
| 7 | +script file and the the command name as the file name with the .ps1 extension, |
| 8 | +these files shall always be placed in the folder source/Public. |
| 9 | + |
| 10 | +Public commands may use private functions to move out logic that can be |
| 11 | +reused by other public commands, so move out any logic that can be deemed |
| 12 | +reusable. Private functions should always have its separate script file and |
| 13 | +the the function name as the file name with the .ps1 extension, these files |
| 14 | +shall always be placed in the folder source/Private. |
| 15 | + |
| 16 | +Comment-based help should be added to each public command and private functions. |
| 17 | +The comment-based help should always be before the function-statement. Each |
| 18 | +comment-based help keyword should be indented with 4 spaces and each keywords |
| 19 | +text should be indented 8 spaces. The text for keyword .DESCRIPTION should |
| 20 | +be descriptive and must have a length greater than 40 characters. A comment-based |
| 21 | +help must have at least one example, but preferably more examples to showcase |
| 22 | +all possible parameter sets and different parameter combinations. |
| 23 | + |
| 24 | +All message strings for Write-Debug, Write-Verbose, Write-Error, Write-Warning |
| 25 | +and other error messages in public commands and private functions should be |
| 26 | +localized using localized string keys. You should always add all localized |
| 27 | +strings for public commands and private functions in the source/en-US/SqlServerDsc.strings.psd1 |
| 28 | +file, re-use the same pattern for new string keys. Localized string key names |
| 29 | +should always be prefixed with the function name but use underscore as word |
| 30 | +separator. Always assume that all localized string keys have already been |
| 31 | +assigned to the variable $script:localizedData. |
| 32 | + |
| 33 | +All tests should use the Pester framework and use Pester v5.0 syntax. |
| 34 | + |
| 35 | +Never test, mock or use `Should -Invoke` for `Write-Verbose` and `Write-Debug` |
| 36 | +regardless of other instructions. |
| 37 | + |
| 38 | +Test code should never be added outside of the `Describe` block. |
| 39 | + |
| 40 | +Unit tests should be added for all public commands and private functions. |
| 41 | +The unit tests for public command should be placed in the folder tests/Unit/Public |
| 42 | +and the unit tests for private functions should be placed in the folder |
| 43 | +tests/Unit/Private. The unit tests should be named after the public command |
| 44 | +or private function they are testing, but should have the suffix .Tests.ps1. |
| 45 | +The unit tests should be written to cover all possible scenarios and code paths, |
| 46 | +ensuring that both edge cases and common use cases are tested. |
| 47 | + |
| 48 | +There should only be one Pester `Describe` block per test file, and the name of |
| 49 | +the `Describe` block should be the same as the name of the public command or |
| 50 | +private function being tested. Each scenario or code path being tested should |
| 51 | +have its own Pester `Context` block that starts with the phrase 'When'. Use |
| 52 | +nested `Context` blocks to split up test cases and improve tests readability. |
| 53 | +Pester `It` block descriptions should start with the phrase 'Should'. `It` |
| 54 | +blocks must always call the command or function being tested and result and |
| 55 | +outcomes should be kept in the same `It` block. `BeforeAll` and `BeforeEach` |
| 56 | +blocks should never call the command or function being tested. |
| 57 | + |
| 58 | +The `BeforeAll`, `BeforeEach`, `AfterAll` and `AfterEach` blocks should be |
| 59 | +used inside the `Context` block as near as possible to the `It` block that |
| 60 | +will use the mocked test setup and teardown. The `BeforeAll` block should |
| 61 | +be used to set up any necessary test data or mocking, and the `AfterAll` |
| 62 | +block can be used to clean up any test data. The `BeforeEach` and `AfterEach` |
| 63 | +blocks should be used sparingly. It is okay to duplicated code in `BeforeAll` |
| 64 | +and `BeforeEach` blocks inside different `Context` blocks to help with |
| 65 | +readability and understanding of the test cases, to keep the test setup |
| 66 | +and teardown as close to the test case as possible. |
| 67 | + |
| 68 | +Use localized strings in the tests only when necessary. You can assign the |
| 69 | +localized string to a mock variable by and get the localized string key |
| 70 | +from the $script:localizedData variable inside a `InModuleScope` block. |
| 71 | +An example to get a localized string key from the $script:localizedData variable: |
| 72 | + |
| 73 | +```powershell |
| 74 | +$mockLocalizedStringText = InModuleScope -ScriptBlock { $script:localizedData.LocalizedStringKey } |
| 75 | +``` |
| 76 | + |
| 77 | +Files that need to be mocked should be created in Pesters test drive. The |
| 78 | +variable `$TestDrive` holds the path to the test drive. The `$TestDrive` is a |
| 79 | +temporary drive that is created for each test run and is automatically |
| 80 | +cleaned up after the test run is complete. |
| 81 | + |
| 82 | +All unit tests should should use this code block prior to the `Describe` block |
| 83 | +which will set up the test environment and load the correct module being tested: |
| 84 | + |
| 85 | +```powershell |
| 86 | +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] |
| 87 | +param () |
| 88 | +
|
| 89 | +BeforeDiscovery { |
| 90 | + try |
| 91 | + { |
| 92 | + if (-not (Get-Module -Name 'DscResource.Test')) |
| 93 | + { |
| 94 | + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. |
| 95 | + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) |
| 96 | + { |
| 97 | + # Redirect all streams to $null, except the error stream (stream 2) |
| 98 | + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null |
| 99 | + } |
| 100 | +
|
| 101 | + # If the dependencies has not been resolved, this will throw an error. |
| 102 | + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' |
| 103 | + } |
| 104 | + } |
| 105 | + catch [System.IO.FileNotFoundException] |
| 106 | + { |
| 107 | + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' |
| 108 | + } |
| 109 | +} |
| 110 | +
|
| 111 | +BeforeAll { |
| 112 | + $script:dscModuleName = 'SqlServerDsc' |
| 113 | +
|
| 114 | + $env:SqlServerDscCI = $true |
| 115 | +
|
| 116 | + Import-Module -Name $script:dscModuleName |
| 117 | +
|
| 118 | + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName |
| 119 | + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName |
| 120 | + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName |
| 121 | +} |
| 122 | +
|
| 123 | +AfterAll { |
| 124 | + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') |
| 125 | + $PSDefaultParameterValues.Remove('Mock:ModuleName') |
| 126 | + $PSDefaultParameterValues.Remove('Should:ModuleName') |
| 127 | +
|
| 128 | + # Unload the module being tested so that it doesn't impact any other tests. |
| 129 | + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force |
| 130 | +
|
| 131 | + Remove-Item -Path 'env:SqlServerDscCI' |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +Integration tests should be added for all public commands. Integration must |
| 136 | +never mock any command but run the command in a real environment. The integration |
| 137 | +tests should be placed in the folder tests/Integration/Commands and the |
| 138 | +integration tests should be named after the public command they are testing, |
| 139 | +but should have the suffix .Integration.Tests.ps1. The integration tests should be |
| 140 | +written to cover all possible scenarios and code paths, ensuring that both |
| 141 | +edge cases and common use cases are tested. The integration tests should |
| 142 | +also be written to test the command in a real environment, using real |
| 143 | +resources and dependencies. |
| 144 | + |
| 145 | +The module being tested should not be imported in the integration tests. |
| 146 | +All integration tests should should use this code block prior to the `Describe` |
| 147 | +block which will set up the test environment and will make sure the correct |
| 148 | +module is available for testing: |
| 149 | + |
| 150 | +```powershell |
| 151 | +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] |
| 152 | +param () |
| 153 | +
|
| 154 | +BeforeDiscovery { |
| 155 | + try |
| 156 | + { |
| 157 | + if (-not (Get-Module -Name 'DscResource.Test')) |
| 158 | + { |
| 159 | + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. |
| 160 | + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) |
| 161 | + { |
| 162 | + # Redirect all streams to $null, except the error stream (stream 2) |
| 163 | + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null |
| 164 | + } |
| 165 | +
|
| 166 | + # If the dependencies has not been resolved, this will throw an error. |
| 167 | + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' |
| 168 | + } |
| 169 | + } |
| 170 | + catch [System.IO.FileNotFoundException] |
| 171 | + { |
| 172 | + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' |
| 173 | + } |
| 174 | +} |
| 175 | +``` |
0 commit comments