Skip to content

Commit 6600eda

Browse files
📖 [Docs]: PowerShell module test profiles now match recursive discovery (#98)
PowerShell module authors can now choose a documented Simple, Standard, or Advanced test profile, with Process-PSModule discovery and trigger behavior described consistently across the authoring and framework guidance. ## New: Three module test profiles Simple keeps one root-level `./tests/<ModuleName>.Tests.ps1` suite. Standard keeps one root-level `./tests/<Group>.Tests.ps1` suite per public function group, while allowing separately named root-level suites for ungrouped functions and cross-cutting behavior. Advanced is recommended when independent configurations, containers, or ordinary test files should be organized in recursively discovered subdirectories. These are documentation conventions over one filesystem discovery engine, not selectable modes. ## Changed: Framework discovery and triggering are explicit Process-PSModule applies precedence independently in every directory: exactly one `*.Configuration.ps1` is selected first and suppresses sibling containers and tests; more than one configuration errors. Otherwise, one or more `*.Container.ps1` files are selected and suppress sibling tests. Otherwise, all `*.Tests.ps1` files are selected. Layouts may mix across directories because child directories are inspected independently. Every discovered artifact needs a unique prefix before its first dot because that prefix becomes `TestName`. Only root `tests/BeforeAll.ps1` and `tests/AfterAll.ps1` are special workflow phases. `.github/PSModule.yml` exposes no profile, layout, or suite-matrix selector; `Settings.Test.Module.Suites` is computed internally from module-local files. Recursive discovery is separate from important-file triggering. The default `ImportantFilePatterns` match only `^src/` and `^README\.md$`, so test-only changes do not enter the important-change build, test, and publish path unless a repository adds `^tests/` and any relevant settings or workflow paths while retaining the defaults it still needs. --- <details> <summary>Technical details</summary> - Adds the general PowerShell test profile guidance under `src/docs/Coding-Standards/PowerShell/Testing.md` and links it from the generated index and site navigation. - Keeps exact recursive discovery precedence, sibling suppression, unique naming, and root-only phase mechanics in Process-PSModule framework documentation. - Aligns Process-PSModule repository structure, pipeline stages, usage, and configuration pages with recursive module-local discovery. - Marks internal `*.Suites` matrices as computed workflow outputs rather than authorable settings. - Documents that test-only changes require `^tests/` in `ImportantFilePatterns` to trigger the important-change path and shows settings/workflow path examples. </details> <details> <summary>Relevant issues (or links)</summary> - No linked issue — implementation was explicitly requested by the release integration session. </details> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent f156c36 commit 6600eda

7 files changed

Lines changed: 123 additions & 27 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: PowerShell Testing
3+
description: Pester test naming and the Simple, Standard, and Advanced profiles for PowerShell modules.
4+
---
5+
6+
# PowerShell Testing
7+
8+
PowerShell tests build on the [testing baseline](../Testing.md): they are test-first, locally runnable, deterministic, isolated, and automated in CI. Use [Pester](https://pester.dev/) and name ordinary test files `*.Tests.ps1`.
9+
10+
## Module test profiles
11+
12+
Simple, Standard, and Advanced are documentation profiles: conventions for arranging module-local tests, not selectable Process-PSModule modes. All three feed the same filesystem discovery engine; there is no layout setting in `.github/PSModule.yml`.
13+
14+
Choose the smallest profile that keeps the suite easy to navigate.
15+
16+
### Simple
17+
18+
Use one root-level test file named after the module:
19+
20+
```text
21+
tests/
22+
└── <ModuleName>.Tests.ps1
23+
```
24+
25+
The Simple layout fits a module whose tests remain readable as one suite.
26+
27+
### Standard
28+
29+
Use one root-level test file per public function group:
30+
31+
```text
32+
tests/
33+
├── <GroupOne>.Tests.ps1
34+
├── <GroupTwo>.Tests.ps1
35+
└── <Behavior>.Tests.ps1 (optional)
36+
```
37+
38+
`<Group>` names the public function group the file covers. When public functions are organized under `src/functions/public/<Group>/`, use the same group name for the test file.
39+
40+
Ungrouped public functions and cross-cutting module behavior may use separate root-level `*.Tests.ps1` suites where appropriate. Name each suite after the functions or behavior it covers.
41+
42+
### Advanced
43+
44+
The Advanced profile is recommended when parts of the suite need independent Pester configurations, containers, or ordinary test files. Organize those parts in subdirectories:
45+
46+
```text
47+
tests/
48+
├── Unit/ # No configuration or containers: all test files
49+
│ ├── GroupOne.Tests.ps1
50+
│ └── GroupTwo.Tests.ps1
51+
├── Integration/
52+
│ └── Integration.Configuration.ps1
53+
└── Compatibility/
54+
├── Linux.Container.ps1
55+
└── Windows.Container.ps1
56+
```
57+
58+
Different directories may use different forms. Process-PSModule recursively applies its [per-directory discovery precedence](../../Frameworks/Process-PSModule/pipeline-stages.md#module-local-test-discovery), including sibling suppression, unique test-name requirements, and root-only workflow phases.

‎src/docs/Coding-Standards/PowerShell/index.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This standard builds on the [language-agnostic baseline](../index.md); where the
1616
| [Functions](Functions.md) | Advanced functions — CmdletBinding, typed and validated parameters, pipeline blocks, ShouldProcess, and required comment-based help. |
1717
| [Classes](Classes.md) | When to reach for a PowerShell class, and how to structure its members, constructors, and documentation. |
1818
| [Scripts](Scripts.md) | Structure for standalone .ps1 scripts — requirements, parameters, help, and keeping the script thin. |
19+
| [PowerShell Testing](Testing.md) | Pester test naming and the Simple, Standard, and Advanced profiles for PowerShell modules. |
1920
| [Messaging](Messaging.md) | Write-Verbose for user-facing operational progress and normal troubleshooting; Write-Debug for developer-focused internals and deep diagnostics. |
2021
| [Version Constraints](Version-Constraints.md) | Express module and package version constraints as NuGet version ranges — the canonical notation across PSResourceGet, .NET package references, and (mapped) #Requires and module manifests. |
2122
| [Module Requirements](Requires-Modules.md) | Valid `#Requires -Modules` version specifications — minimum, major-lock (with the `N.*` wildcard), exact, and GUID identity pinning — with an executable proof. |
@@ -80,4 +81,4 @@ Beyond the basics, these language-specific habits keep PowerShell correct and fa
8081
The toolchain enforces this standard in CI — it does not define it. The rules above are the source of truth; each tool's configuration is derived from them:
8182

8283
- **[PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer)** is the linter and formatter; its settings are derived from this standard, so passing it cleanly means matching the standard. Let it format — do not hand-format.
83-
- **[Pester](https://pester.dev/)** is the test framework; test files are named `*.Tests.ps1`. See the [Testing baseline](../Testing.md).
84+
- **[Pester](https://pester.dev/)** is the test framework; test files are named `*.Tests.ps1`. See [PowerShell Testing](Testing.md) for module test layouts.

‎src/docs/Frameworks/Process-PSModule/configuration.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ Plan phase, Process-PSModule enriches this input into an internal runtime `Setti
1313
consume. Internal runtime paths in workflow docs (for example, `Settings.Publish.Module.Resolution.*`) describe that
1414
enriched inter-workflow contract, not a different authoring format for repository settings files.
1515

16+
Simple, Standard, and Advanced test profiles are repository conventions, not settings. `.github/PSModule.yml` has no
17+
layout or suite-matrix selector; Process-PSModule [discovers the files under `tests/` recursively](pipeline-stages.md#module-local-test-discovery)
18+
and computes its internal `Settings.Test.Module.Suites` matrix from them.
19+
20+
Test discovery and change triggering are separate. The default `ImportantFilePatterns` match only `^src/` and
21+
`^README\.md$`, so a test-only change does not enter the important-change build, test, and publish path. Repositories
22+
that need test or automation changes to exercise that path must [add `^tests/` and any relevant settings or workflow
23+
paths](usage.md#customizing-important-file-patterns) while retaining every default they still need.
24+
1625
The following settings are available in the settings file:
1726

1827
| Name | Type | Description | Default |

‎src/docs/Frameworks/Process-PSModule/pipeline-stages.md‎

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ situational awareness, it calculates the next module version.
1919

2020
The user-facing settings file stays in `.github/PSModule.yml`. The workflow enriches that input into an internal runtime
2121
`Settings` object passed between jobs. In this runtime contract, execution decisions are phase-owned (`*.Enabled`), test
22-
suite matrices are defined under each owning test phase, and resolved version metadata is stored under
23-
`Settings.Publish.Module.Resolution`.
22+
suite matrices are computed under each owning test phase, and resolved version metadata is stored under
23+
`Settings.Publish.Module.Resolution`. The `*.Suites` values are workflow outputs, not authorable layout settings.
2424

2525
### Internal runtime settings contract
2626

@@ -37,9 +37,9 @@ suite matrices are defined under each owning test phase, and resolved version me
3737
| `Settings.Test.CodeCoverage.Enabled` | Whether code coverage aggregation/enforcement runs. |
3838
| `Settings.Publish.Module.Enabled` | Whether module publication/release runs. |
3939
| `Settings.Publish.Site.Enabled` | Whether documentation publication runs. |
40-
| `Settings.Test.SourceCode.Suites` | Source-code test suite matrix. |
41-
| `Settings.Test.PSModule.Suites` | Framework test suite matrix. |
42-
| `Settings.Test.Module.Suites` | Module-local test suite matrix. |
40+
| `Settings.Test.SourceCode.Suites` | Computed source-code test suite matrix. |
41+
| `Settings.Test.PSModule.Suites` | Computed framework test suite matrix. |
42+
| `Settings.Test.Module.Suites` | Computed module-local test suite matrix. |
4343
| `Settings.Publish.Module.Resolution.Version` | Resolved semantic version used for build and publish. |
4444
| `Settings.Publish.Module.Resolution.Prerelease` | Whether the resolved version is prerelease. |
4545
| `Settings.Publish.Module.Resolution.FullVersion` | Resolved full version string. |
@@ -109,24 +109,40 @@ The [PSModule - SourceCode tests](https://github.com/PSModule/Process-PSModule/b
109109

110110
[workflow](https://github.com/PSModule/Process-PSModule/blob/main/.github/workflows/Test-ModuleLocal.yml)
111111

112-
- Imports and tests the module in parallel (matrix) using Pester tests from the module repository.
112+
- Imports and tests the module in parallel (matrix) using module-local Pester tests.
113+
- Discovers module-local tests recursively under `tests/`, applying the [per-directory precedence](#module-local-test-discovery) independently at every level.
113114
- Module test files declare a Pester **6.x** requirement via `#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.0.0'; MaximumVersion = '6.*' }` — a convention module authors add to each `*.Tests.ps1`, not something this pipeline injects. The [Invoke-Pester](https://github.com/PSModule/Invoke-Pester) action installs a matching `6.x`, so minor and patch updates flow in automatically while a new major stays a deliberate, reviewed change.
114115
- Supports setup and teardown scripts executed via separate dedicated jobs:
115-
- `BeforeAll`: Runs once before all test matrix jobs to set up the test environment (e.g., deploy infrastructure, download test data).
116-
- `AfterAll`: Runs once after all test matrix jobs complete to clean up the test environment (e.g., remove test resources, clean up databases).
117-
- Setup/teardown scripts are automatically detected in test directories and executed with the same environment variables as the tests.
118-
- This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) evaluate the results of the tests.
116+
- `BeforeAll`: Runs root `tests/BeforeAll.ps1` once before all test matrix jobs to set up the test environment (e.g., deploy infrastructure, download test data).
117+
- `AfterAll`: Runs root `tests/AfterAll.ps1` once after all test matrix jobs complete to clean up the test environment (e.g., remove test resources, clean up databases).
118+
- Setup and teardown detection is not recursive; nested files with those names are not workflow phases.
119+
- This produces a JSON-based report that is used by [Get-PesterTestResults](#get-test-results) to evaluate the results of the tests.
120+
121+
### Module-local test discovery
122+
123+
Simple, Standard, and Advanced are [documentation profiles](../../Coding-Standards/PowerShell/Testing.md#module-test-profiles), not selectable workflow modes. The same discovery engine handles every profile. `.github/PSModule.yml` has no test-layout or suite-matrix setting; `Settings.Test.Module.Suites` is computed internally from the repository files.
124+
125+
Process-PSModule inspects `tests/` recursively. Within each directory it uses the first matching form:
126+
127+
1. Exactly one `*.Configuration.ps1`. Discovery fails when a directory contains more than one. When selected, sibling `*.Container.ps1` and `*.Tests.ps1` files are not independently selected.
128+
2. Otherwise, one or more `*.Container.ps1`. When selected, sibling `*.Tests.ps1` files are not independently selected.
129+
3. Otherwise, all `*.Tests.ps1`.
130+
131+
The selected form takes precedence only in that directory. Child directories are still inspected independently, so a repository may mix configurations, containers, and ordinary test files across different directories.
132+
133+
Every discovered artifact needs a unique prefix before its first dot because that prefix becomes `TestName`. For example, `Users.Unit.Tests.ps1` and `Users.Integration.Tests.ps1` both become `Users`; use distinct prefixes such as `UsersUnit` and `UsersIntegration`.
119134

120135
### Setup and Teardown Scripts
121136

122137
The workflow supports automatic execution of setup and teardown scripts for module tests:
123138

124-
- Scripts are automatically detected and executed if present.
139+
- The exact root paths `tests/BeforeAll.ps1` and `tests/AfterAll.ps1` are detected and executed if present.
125140
- If no scripts are found, the workflow continues normally.
141+
- Detection is not recursive.
126142

127143
#### Setup - `BeforeAll.ps1`
128144

129-
- Place in your test directories (`tests/BeforeAll.ps1`).
145+
- Place at the root test path `tests/BeforeAll.ps1`.
130146
- Runs once before all test matrix jobs to prepare the test environment.
131147
- Deploy test infrastructure, download test data, initialize databases, or configure services.
132148
- Has access to the same environment variables as your tests (secrets, GitHub token, etc.).
@@ -143,7 +159,7 @@ Write-Host "Test environment ready!"
143159

144160
#### Teardown - `AfterAll.ps1`
145161

146-
- Place in your test directories (`tests/AfterAll.ps1`).
162+
- Place at the root test path `tests/AfterAll.ps1`.
147163
- Runs once after all test matrix jobs complete to clean up the test environment.
148164
- Remove test resources, clean up databases, stop services, or upload artifacts.
149165
- Has access to the same environment variables as your tests.

‎src/docs/Frameworks/Process-PSModule/repository-structure.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,23 @@ Process-PSModule expects repositories to follow the staged layout produced by Te
2626
├── icon/ # Icon assets linked from manifest and documentation
2727
│ └── icon.png # Default module icon (PNG format)
2828
├── src/ # Module source, see "Module source code structure" below
29-
├── tests/ # Pester suites executed during validation
29+
├── tests/ # Pester suites; the Simple layout is shown
3030
│ ├── AfterAll.ps1 (optional) # Cleanup script for ModuleLocal runs
3131
│ ├── BeforeAll.ps1 (optional) # Setup script for ModuleLocal runs
32-
│ └── <ModuleName>.Tests.ps1 # Primary test entry point
32+
│ └── <ModuleName>.Tests.ps1 # Simple: one root-level module suite
3333
├── .gitattributes # Normalizes line endings across platforms
3434
├── .gitignore # Excludes build artifacts from source control
3535
├── LICENSE # License text surfaced in manifest metadata
3636
└── README.md # Repository overview rendered on GitHub and docs landing
3737
```
3838

39+
The tree shows the [Simple PowerShell test profile](../../Coding-Standards/PowerShell/Testing.md#simple), not an exclusive test-file shape. Standard keeps one root-level `tests/<Group>.Tests.ps1` file per public function group. Advanced uses recursively discovered subdirectories, and layouts may mix across directories. Process-PSModule defines the exact [per-directory precedence and sibling suppression](pipeline-stages.md#module-local-test-discovery).
40+
41+
These names describe repository conventions, not settings. `.github/PSModule.yml` does not select a test profile. The optional `tests/BeforeAll.ps1` and `tests/AfterAll.ps1` files are root-only workflow phases and are not discovered recursively.
42+
3943
Key expectations:
4044

41-
- Keep at least one exported function under `src/functions/public/` and corresponding tests in `tests/`.
45+
- Keep at least one exported function under `src/functions/public/` and corresponding tests in `tests/` using a [documented test profile](../../Coding-Standards/PowerShell/Testing.md#module-test-profiles).
4246
- Keep documentation site configuration in `.github/zensical.toml`.
4347
- Optional folders (`assemblies`, `formats`, `types`, `variables`, and others) are processed automatically when present.
4448
- Markdown files in `src/functions/public` subfolders become documentation pages alongside generated help.

‎src/docs/Frameworks/Process-PSModule/usage.md‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ $env:CONFLUENCE_SITE # from the "variables" map (not masked)
137137

138138
The same `TestData` keys are exported before every module-local phase runs:
139139

140-
- `BeforeAll-ModuleLocal` runs `tests/BeforeAll.ps1` before the module-local test matrix.
141-
- `Test-ModuleLocal` runs the module's Pester tests.
142-
- `AfterAll-ModuleLocal` runs `tests/AfterAll.ps1` after the module-local test matrix, including cleanup paths.
140+
- `BeforeAll-ModuleLocal` runs root `tests/BeforeAll.ps1` before the module-local test matrix.
141+
- `Test-ModuleLocal` discovers and runs module-local Pester tests recursively.
142+
- `AfterAll-ModuleLocal` runs root `tests/AfterAll.ps1` after the module-local test matrix, including cleanup paths.
143143

144-
Setup scripts, tests, and teardown scripts should therefore use the same environment variable names.
144+
Setup and teardown detection is not recursive. These root scripts and the discovered tests should use the same environment variable names.
145145
If `$env:<name>` is available in one phase but missing in another, treat that as a Process-PSModule
146146
propagation bug rather than a caller contract difference.
147147

@@ -223,11 +223,11 @@ This table shows when each job runs based on the trigger scenario:
223223

224224
## Important file change detection
225225

226-
The workflow automatically detects whether a pull request contains changes to "important" files that warrant a new
227-
release. This prevents unnecessary releases when only non-functional files (such as workflow configurations, linter
228-
settings, or test files) are modified.
226+
The workflow automatically detects whether a pull request contains changes to "important" files that should enter the
227+
build, test, and publish path. This prevents unnecessary work and releases when only files outside the configured
228+
patterns are modified.
229229

230-
### Files that trigger releases
230+
### Files that trigger the important-change path
231231

232232
By default, the following regular expression patterns identify important files:
233233

@@ -244,11 +244,18 @@ To override the default patterns, set `ImportantFilePatterns` in your settings f
244244
ImportantFilePatterns:
245245
- '^src/'
246246
- '^README\.md$'
247-
- '^examples/'
247+
- '^tests/'
248+
- '^\.github/PSModule\.yml$'
249+
- '^\.github/workflows/'
248250
```
249251

250252
When configured, the provided list fully replaces the defaults. Include the default patterns in your list if you still
251-
want them to trigger releases.
253+
want them to trigger the build, test, and publish path.
254+
255+
Recursive [module-local test discovery](pipeline-stages.md#module-local-test-discovery) does not change this trigger.
256+
With the defaults, a test-only change does not run the important-change build, test, and publish stages because
257+
`^tests/` is not matched. Add `^tests/` when those changes must exercise the path, plus each settings, workflow, or
258+
other automation path whose changes need the same validation. Include only paths that should trigger all three stages.
252259

253260
To disable file-change triggering entirely (so that no file changes ever trigger a release), set an empty list in the
254261
settings file:

‎src/zensical.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ nav = [
104104
{"Functions" = "Coding-Standards/PowerShell/Functions.md"},
105105
{"Classes" = "Coding-Standards/PowerShell/Classes.md"},
106106
{"Scripts" = "Coding-Standards/PowerShell/Scripts.md"},
107+
{"Testing" = "Coding-Standards/PowerShell/Testing.md"},
107108
{"Messaging" = "Coding-Standards/PowerShell/Messaging.md"},
108109
{"Version Constraints" = "Coding-Standards/PowerShell/Version-Constraints.md"},
109110
]},

0 commit comments

Comments
 (0)