microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
docs/architecture/testing.md
216lines · modecode
| 1 | --- |
| 2 | title: Testing Architecture |
| 3 | description: PowerShell Pester test infrastructure and conventions |
| 4 | sidebar_position: 4 |
| 5 | author: Microsoft |
| 6 | ms.date: 2026-01-22 |
| 7 | ms.topic: concept |
| 8 | --- |
| 9 | |
| 10 | ## Overview |
| 11 | |
| 12 | HVE Core uses Pester 5.x for PowerShell testing with a mirror directory structure that maps production scripts to their corresponding test files. The test infrastructure supports isolated unit testing through mock utilities and enforces an 80% code coverage threshold. |
| 13 | |
| 14 | ## Directory Structure |
| 15 | |
| 16 | Test files follow a mirror pattern where each script directory has a corresponding `tests/` subdirectory: |
| 17 | |
| 18 | ```text |
| 19 | scripts/ |
| 20 | ├── collections/ |
| 21 | │ └── *.ps1 |
| 22 | ├── extension/ |
| 23 | │ ├── Package-Extension.ps1 |
| 24 | │ └── Prepare-Extension.ps1 |
| 25 | ├── lib/ |
| 26 | │ └── Get-VerifiedDownload.ps1 |
| 27 | ├── linting/ |
| 28 | │ └── *.ps1 |
| 29 | ├── plugins/ |
| 30 | │ └── *.ps1 |
| 31 | ├── security/ |
| 32 | │ └── *.ps1 |
| 33 | └── tests/ |
| 34 | ├── collections/ |
| 35 | ├── extension/ |
| 36 | ├── lib/ |
| 37 | ├── linting/ |
| 38 | ├── plugins/ |
| 39 | ├── security/ |
| 40 | ├── Fixtures/ |
| 41 | ├── Mocks/ |
| 42 | │ └── GitMocks.psm1 |
| 43 | └── pester.config.ps1 |
| 44 | ``` |
| 45 | |
| 46 | Test files use the `.Tests.ps1` suffix convention, enabling automatic discovery by Pester. |
| 47 | |
| 48 | ## Pester Configuration |
| 49 | |
| 50 | The configuration file at [scripts/tests/pester.config.ps1](https://github.com/microsoft/hve-core/blob/main/scripts/tests/pester.config.ps1) defines test execution behavior: |
| 51 | |
| 52 | ```powershell |
| 53 | # Key configuration settings |
| 54 | $configuration.Run.TestExtension = '.Tests.ps1' |
| 55 | $configuration.Filter.ExcludeTag = @('Integration', 'Slow') |
| 56 | $configuration.CodeCoverage.CoveragePercentTarget = 80 |
| 57 | ``` |
| 58 | |
| 59 | ### Coverage Configuration |
| 60 | |
| 61 | Code coverage analyzes scripts in production directories while excluding test files: |
| 62 | |
| 63 | | Setting | Value | |
| 64 | |-------------------|---------------------| |
| 65 | | Coverage target | 80% minimum | |
| 66 | | Output format | JaCoCo XML | |
| 67 | | Output path | `logs/coverage.xml` | |
| 68 | | Excluded patterns | `*.Tests.ps1` | |
| 69 | |
| 70 | Coverage directories include `linting/`, `security/`, `lib/`, `extension/`, `plugins/`, `collections/`, and `tests/`. |
| 71 | |
| 72 | ### Test Output |
| 73 | |
| 74 | | Output Type | Format | Path | |
| 75 | |-----------------|----------|---------------------------| |
| 76 | | Test results | NUnitXml | `logs/pester-results.xml` | |
| 77 | | Coverage report | JaCoCo | `logs/coverage.xml` | |
| 78 | |
| 79 | ## Test Utilities |
| 80 | |
| 81 | ### LintingHelpers Module |
| 82 | |
| 83 | The [LintingHelpers.psm1](https://github.com/microsoft/hve-core/blob/main/scripts/linting/Modules/LintingHelpers.psm1) module provides shared functions for linting scripts and tests: |
| 84 | |
| 85 | | Function | Purpose | |
| 86 | |---------------------------|-----------------------------------------------------------------| |
| 87 | | `Get-ChangedFilesFromGit` | Detects changed files using merge-base with fallback strategies | |
| 88 | | `Get-FilesRecursive` | Finds files via `git ls-files` with `Get-ChildItem` fallback | |
| 89 | | `Get-GitIgnorePatterns` | Parses `.gitignore` into PowerShell wildcard patterns | |
| 90 | | `Write-GitHubAnnotation` | Writes GitHub Actions annotations for errors and warnings | |
| 91 | | `Set-GitHubOutput` | Sets GitHub Actions output variables | |
| 92 | | `Set-GitHubEnv` | Sets GitHub Actions environment variables | |
| 93 | |
| 94 | ### GitMocks Module |
| 95 | |
| 96 | The [GitMocks.psm1](https://github.com/microsoft/hve-core/blob/main/scripts/tests/Mocks/GitMocks.psm1) module provides reusable mock helpers for Git CLI and GitHub Actions testing. |
| 97 | |
| 98 | #### Environment Management |
| 99 | |
| 100 | | Function | Purpose | |
| 101 | |------------------------------------|---------------------------------------------------------| |
| 102 | | `Save-GitHubEnvironment` | Saves current GitHub Actions environment variables | |
| 103 | | `Restore-GitHubEnvironment` | Restores saved environment state | |
| 104 | | `Initialize-MockGitHubEnvironment` | Creates mock GitHub Actions environment with temp files | |
| 105 | | `Clear-MockGitHubEnvironment` | Removes GitHub Actions environment variables | |
| 106 | | `Remove-MockGitHubFiles` | Cleans up temp files from mock initialization | |
| 107 | |
| 108 | #### Git Mocks |
| 109 | |
| 110 | | Function | Purpose | |
| 111 | |-------------------------------|---------------------------------------------------| |
| 112 | | `Initialize-GitMocks` | Sets up standard git command mocks for a module | |
| 113 | | `Set-GitMockChangedFiles` | Updates files returned by git diff mock | |
| 114 | | `Set-GitMockMergeBaseFailure` | Simulates merge-base failure for fallback testing | |
| 115 | |
| 116 | #### Test Data |
| 117 | |
| 118 | | Function | Purpose | |
| 119 | |---------------------------|---------------------------------------------------| |
| 120 | | `New-MockFileList` | Generates mock file paths for testing | |
| 121 | | `Get-MockGitDiffScenario` | Returns predefined scenarios for git diff testing | |
| 122 | |
| 123 | ### Environment Save/Restore Pattern |
| 124 | |
| 125 | Tests that modify environment variables follow this pattern: |
| 126 | |
| 127 | ```powershell |
| 128 | BeforeAll { |
| 129 | Import-Module "$PSScriptRoot/../Mocks/GitMocks.psm1" -Force |
| 130 | } |
| 131 | |
| 132 | BeforeEach { |
| 133 | Save-GitHubEnvironment |
| 134 | $script:MockFiles = Initialize-MockGitHubEnvironment |
| 135 | } |
| 136 | |
| 137 | AfterEach { |
| 138 | Remove-MockGitHubFiles -MockFiles $script:MockFiles |
| 139 | Restore-GitHubEnvironment |
| 140 | } |
| 141 | ``` |
| 142 | |
| 143 | ## Running Tests |
| 144 | |
| 145 | ### npm Scripts |
| 146 | |
| 147 | | Command | Description | |
| 148 | |-------------------|----------------------| |
| 149 | | `npm run test:ps` | Run all Pester tests | |
| 150 | |
| 151 | ### Direct Pester Invocation |
| 152 | |
| 153 | Run tests with default configuration: |
| 154 | |
| 155 | ```powershell |
| 156 | Invoke-Pester -Configuration (& ./scripts/tests/pester.config.ps1) |
| 157 | ``` |
| 158 | |
| 159 | Run tests with code coverage: |
| 160 | |
| 161 | ```powershell |
| 162 | Invoke-Pester -Configuration (& ./scripts/tests/pester.config.ps1 -CodeCoverage) |
| 163 | ``` |
| 164 | |
| 165 | Run tests in CI mode with exit codes and NUnit output: |
| 166 | |
| 167 | ```powershell |
| 168 | Invoke-Pester -Configuration (& ./scripts/tests/pester.config.ps1 -CI -CodeCoverage) |
| 169 | ``` |
| 170 | |
| 171 | Run a specific test file: |
| 172 | |
| 173 | ```powershell |
| 174 | Invoke-Pester -Path ./scripts/tests/linting/Invoke-PSScriptAnalyzer.Tests.ps1 |
| 175 | ``` |
| 176 | |
| 177 | ### Test Utility Scripts |
| 178 | |
| 179 | Two wrapper scripts in `scripts/tests/` streamline test execution: |
| 180 | |
| 181 | * `Invoke-PesterTests.ps1` orchestrates full test runs with configuration loading, code coverage, CI output formatting, and result file generation. The `npm run test:ps` command calls this script. |
| 182 | * `Get-ChangedTestFiles.ps1` identifies test files affected by recent changes, enabling targeted test runs during development or in pull request workflows. |
| 183 | |
| 184 | See [scripts/tests/README.md](https://github.com/microsoft/hve-core/blob/main/scripts/tests/README.md) for parameters and usage details. |
| 185 | |
| 186 | ## Skills Testing |
| 187 | |
| 188 | Skill scripts use a co-located test pattern instead of the mirror directory structure used by `scripts/`. Each skill contains its own `tests/` subdirectory: |
| 189 | |
| 190 | ```text |
| 191 | .github/skills/<skill-name>/ |
| 192 | ├── scripts/ |
| 193 | │ ├── convert.ps1 |
| 194 | │ └── convert.sh |
| 195 | └── tests/ |
| 196 | └── convert.Tests.ps1 |
| 197 | ``` |
| 198 | |
| 199 | ### Coverage Integration |
| 200 | |
| 201 | The Pester configuration at `scripts/tests/pester.config.ps1` resolves skill scripts from the repository root for code coverage analysis. When you include a skill `tests/` directory in an `Invoke-Pester -Path` argument or test run configuration, Pester discovers the skill test files through the `.Tests.ps1` naming convention. |
| 202 | |
| 203 | Coverage path resolution for skills uses the repository root rather than `$scriptRoot` (which points to `scripts/`): |
| 204 | |
| 205 | ```powershell |
| 206 | $repoRoot = Split-Path $scriptRoot -Parent |
| 207 | $skillScripts = Get-ChildItem -Path (Join-Path $repoRoot '.github/skills') ` |
| 208 | -Include '*.ps1', '*.psm1' -Recurse -File -ErrorAction SilentlyContinue | |
| 209 | Where-Object { $_.FullName -notmatch '\.Tests\.ps1$' } |
| 210 | ``` |
| 211 | |
| 212 | ### Packaging Exclusion |
| 213 | |
| 214 | Co-located `tests/` directories are excluded from the VSIX extension package by `Package-Extension.ps1`. After copying a skill directory, the packaging script removes any `tests/` subdirectories from the destination. |
| 215 | |
| 216 | 🤖 *Crafted with precision by ✨Copilot following brilliant human instruction, then carefully refined by our team of discerning human reviewers.* |
| 217 | |