Skip to content

Commit 8927592

Browse files
docs: document PSModule test state contract
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 3ed8e89 commit 8927592

2 files changed

Lines changed: 108 additions & 5 deletions

File tree

docs/content/guides/pester-migration.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ The migration covers repository and test-set inventory, supported PowerShell
1414
runtime and CI checks, per-file discovery and run isolation, hidden paths,
1515
data-driven tests, setup blocks, mocks, pending tests, name templates,
1616
coverage, `Invoke-Pester` configuration, reserved tags, and validation
17-
reporting. It does not migrate consumer repositories as part of this framework
18-
change.
17+
reporting. It also defines the test-state/data contract: module-local tests
18+
consume the target module already loaded by Process-PSModule and explicitly
19+
load any PSD1 or other fixture data. They must not silently import the module as
20+
a substitute for framework setup. It does not migrate consumer repositories as
21+
part of this framework change.
1922

2023
## Required before declaring a migration complete
2124

2225
- Every test entry point is inventoried and runs on its supported runtime matrix.
26+
- The target module is loaded by the framework before module-local Pester tests;
27+
tests do not hide a missing load with `Import-Module`.
28+
- Every PSD1, JSON, CSV, XML, script, generated fixture, secret, variable, and
29+
service dependency has an owner and explicit loading phase.
30+
- `Invoke-Pester`, `Test-PSModule`, module-local/source workflows,
31+
`BeforeAll`/`AfterAll`, `Expose-TestData`, result/coverage collectors, and
32+
linter result publishing are included in the repository inventory.
2333
- Pester 6.1.0 is imported in local and CI acceptance runs.
2434
- Each test file is self-contained under per-file discovery and run.
2535
- Hidden paths, empty data, duplicate setup blocks, mocks, pending tests, name

skills/pester-migration/SKILL.md

Lines changed: 96 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,96 @@ For each test set, add an inventory row to the migration report:
8686
Use the inventory to find test sets that are not reachable from the default
8787
workflow. A green workflow is not evidence that every test set was migrated.
8888

89+
## Test-state and data contract
90+
91+
Treat the test runner's prepared state as a contract, not an implementation
92+
detail. A module-local Pester test must assume that the target, built module is
93+
already loaded by the framework. It must not silently call `Import-Module`,
94+
`Install-Module`, `Install-PSModule`, or dot-source the target module as a
95+
fallback when the framework did not prepare it. A hidden import can make local
96+
tests pass while the real workflow is misconfigured and can change the module
97+
version or process state being tested.
98+
99+
This does not prohibit a test whose explicit subject is module import,
100+
manifest validity, or module removal. Such a framework or contract test should
101+
say so in its name and use a deliberate, isolated import as the behavior under
102+
test. It must not become setup for unrelated tests.
103+
104+
Every additional fixture is also part of the test-state contract. A test set
105+
must explicitly load its own JSON, CSV, XML, PSD1, script, or generated data,
106+
or document which setup phase provides it. In particular, a PSD1 dataset is
107+
not automatically loaded because it is in the repository:
108+
109+
```powershell
110+
BeforeDiscovery {
111+
$cases = Import-PowerShellDataFile -Path (Join-Path $PSScriptRoot 'Data\Cases.psd1')
112+
}
113+
```
114+
115+
Record fixture ownership and availability in the inventory:
116+
117+
| Fixture or state | Owner | Loaded in | Required environment |
118+
| --- | --- | --- | --- |
119+
| Target module | Process-PSModule or explicit contract test | Framework pre-run | Built module path and version |
120+
| PSD1/JSON/CSV data | Test set or documented setup | `BeforeDiscovery`, `BeforeAll`, or setup job | Relative path and encoding |
121+
| Secrets/variables | Calling workflow | `Expose-TestData` and environment | `TestData` JSON contract |
122+
| Shared service | `tests/BeforeAll.ps1` | Before module-local matrix | Deterministic run-scoped name |
123+
| Cleanup state | `tests/AfterAll.ps1` | Always-run teardown | Same `TestData` and run identity |
124+
125+
Fail loudly when required data or state is absent. Do not use a broad
126+
`try/catch`, an empty default, or an implicit import to turn a missing fixture
127+
into a passing or skipped test.
128+
129+
## Process-PSModule test-adjacent surfaces
130+
131+
For every consumer repository, trace and record these surfaces before editing:
132+
133+
| Surface | Framework contract to verify | Migration check |
134+
| --- | --- | --- |
135+
| `PSModule/Invoke-Pester` | Installs/runs Pester and emits per-suite JSON results and coverage artifacts | Pin/verify Pester 6.1.0, map all inputs to v6 configuration, preserve suite names |
136+
| `Test-PSModule` action | Selects `tests/Module` or `tests/SourceCode`, resolves `outputs/module` or `src`, and passes paths to `Invoke-Pester` | Confirm path selection, exclusions, module state, and test extension |
137+
| Module-local workflow | Downloads the built module, exposes `TestData`, imports the module, then runs module tests | Tests consume the prepared module; no hidden fallback import |
138+
| Source-code workflow | Runs source tests against the checked-out `src` path | Record how source functions/classes are loaded and which fixtures are explicit |
139+
| `BeforeAll-ModuleLocal` | Runs exact root `tests/BeforeAll.ps1` once before module-local jobs | Put shared services/data here only when every matrix job needs them |
140+
| `AfterAll-ModuleLocal` | Runs exact root `tests/AfterAll.ps1` with `always()` | Make cleanup safe after setup/test failure |
141+
| `Expose-TestData` | Converts caller `TestData` JSON into environment variables | Inventory every required variable; do not confuse it with repository fixtures |
142+
| `Get-PesterTestResults` | Downloads `*-TestResults`, expects every configured suite, and fails missing/unexecuted/failed/inconclusive results | Preserve `TestSuiteName`, matrix names, and result counts |
143+
| `Get-PesterCodeCoverage` | Aggregates `*-CodeCoverage` JSON and writes missed-path reports and summaries | Verify JaCoCo/Cobertura format, target, paths, and v6 tracer behavior |
144+
| `Invoke-ScriptAnalyzer` integration | Publishes `PSModuleLint-*` results alongside test results | Treat lint suites as required result artifacts, not Pester test files |
145+
| Repository linter | Checks repository/workflow/Markdown files independently of Pester | Include skill and documentation paths in the repository lint inventory |
146+
147+
The current Process-PSModule flow specifically downloads the built module and
148+
imports it before module-local tests, while `Test-PSModule` resolves source or
149+
module paths for framework suites. The framework's own importability and
150+
manifest tests may import/remove the module because import is their subject;
151+
ordinary consumer tests must not copy that pattern as setup.
152+
153+
Record the `PSModule/Invoke-Pester` action revision separately from the Pester
154+
module version. The current Process-PSModule workflows reference that action at
155+
`v5.1.0`; its revision and its Pester installation policy must both be checked
156+
when adopting Pester 6.1.0. Updating one does not prove that the other changed.
157+
158+
Use this per-repository checklist:
159+
160+
- [ ] All `*.Tests.ps1`, `*.Configuration.ps1`, and `*.Container.ps1` files,
161+
including action and hidden paths, are listed.
162+
- [ ] Each test set is mapped to `Module`, `SourceCode`, action, or external
163+
invocation and its `Run.Path`/`Run.ExcludePath` is recorded.
164+
- [ ] The built target module is loaded by the framework before module-local
165+
tests; tests do not silently load it themselves.
166+
- [ ] Source-code test loading is explicit and documented separately from the
167+
module-local contract.
168+
- [ ] Every PSD1, JSON, CSV, XML, script, generated fixture, secret, variable,
169+
and service dependency has an owner and loading phase.
170+
- [ ] `BeforeAll.ps1`, `AfterAll.ps1`, and `Pester.BeforeContainer.ps1` paths
171+
and scope are recorded.
172+
- [ ] `Invoke-Pester` action inputs, Pester version, suite names, result paths,
173+
coverage paths, filters, and output formats are recorded.
174+
- [ ] `Get-PesterTestResults` expected artifact names include source, framework,
175+
module, and linter suites.
176+
- [ ] Coverage aggregation and its missed-path report are validated.
177+
- [ ] Local, CI, and linter runs use the same intended fixture and module state.
178+
89179
## Step 2: Check versions, runtime, and CI
90180

91181
Run the baseline on the current version before changing files. Save the command,
@@ -135,7 +225,8 @@ BeforeDiscovery {
135225
}
136226
137227
BeforeAll {
138-
Import-Module (Join-Path $PSScriptRoot '..\src\MyModule.psd1') -Force
228+
# Consume the module loaded by the framework.
229+
$command = Get-Command -Name Get-MyThing -ErrorAction Stop
139230
}
140231
141232
Describe 'My command' {
@@ -145,8 +236,10 @@ Describe 'My command' {
145236
}
146237
```
147238

148-
Use `$PSScriptRoot` for paths. Do not rely on `$MyInvocation.MyCommand.Path`,
149-
the current directory, another test file's variables, or discovery order.
239+
Use `$PSScriptRoot` for fixture paths. Do not rely on
240+
`$MyInvocation.MyCommand.Path`, the current directory, another test file's
241+
variables, or discovery order. If the command check fails, fix the workflow's
242+
module preparation rather than adding an import to this test.
150243
Repository-wide bootstrap that every worker needs belongs in
151244
`Pester.BeforeContainer.ps1` at the repository root. Keep it deterministic and
152245
idempotent.

0 commit comments

Comments
 (0)