Choosing what runs
Running everything is the right default. It stops being practical the moment part of your suite is slow, or platform-specific, or only meaningful against a real database.
Pester gives you two different tools for that, and the difference matters: tags decide what gets selected to run, skip marks a test as deliberately not run.
Tagging tests
-Tag goes on Describe, Context or It, and is inherited by everything inside. Your two private-function files are a natural group — they reach into the module with InModuleScope, and they are the tests you would drop first if you only wanted to check the public surface:
Describe 'ConvertTo-AstronomicalUnit' {
Describe 'ConvertTo-AstronomicalUnit' -Tag 'Internal' {
Describe 'Test-PlanetName' {
Describe 'Test-PlanetName' -Tag 'Internal' {
Now you can run the public surface on its own:
Invoke-Pester -Path ./Planetarium -ExcludeTagFilter 'Internal'
Tests Passed: 8, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 7
Or only the internals:
Invoke-Pester -Path ./Planetarium -TagFilter 'Internal'
Tests Passed: 7, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 8
The same two filters exist on the configuration object as Filter.Tag and Filter.ExcludeTag, which is how you would set them in test.ps1 or from a CI variable.
NotRun is not SkippedRead those totals again: the filtered-out tests are counted as NotRun, and Skipped stays at zero.
Pester discovers every test either way — that is how it knows there are fifteen — then runs only the ones your filter selected. NotRun means "not selected". Skipped means "selected, then deliberately passed over", which is the next section. Keeping them in separate columns means a filtered run cannot quietly hide a test you thought was executing.
Tags people reach for in practice: Slow, Integration, Unit, WindowsOnly. The useful test is whether you would ever want to run with the tag, or without it — a tag nobody filters on is just a comment.
Skipping tests
-Skip marks a test as not to be run, while keeping it visible in the output:
It 'Talks to the real API' -Skip {
# not written yet
}
[!] Talks to the real API 1ms
[!] rather than [+] or [-], and the summary counts it under Skipped. That visibility is the entire point: a skipped test nags, whereas a commented-out test is invisible and eventually forgotten.
Where it gets useful is a condition:
It 'Uses the Windows registry' -Skip:(-not $IsWindows) {
# ...
}
On Windows this runs; everywhere else it reports as skipped instead of failing. That is how a cross-platform suite handles the parts that genuinely cannot run everywhere — and it is what keeps the CI matrix in the last module green on all three operating systems.
Both -Skip examples above are illustrations rather than tests to add — Planetarium has nothing that needs skipping yet. The next section does change a real file.
BeforeDiscovery
Here is the catch, and it is the one thing on this page that trips people up.
-Skip:(-not $IsWindows) is evaluated when Pester discovers your tests, not when it runs them. Same for the -ForEach list you wrote back in the private-functions page. So a variable set in BeforeAll is no good to either of them — BeforeAll has not run yet.
BeforeDiscovery is the block that runs during discovery, and it exists precisely for building the data those parameters need:
BeforeDiscovery {
$KnownPlanets = @('Mercury', 'Earth', 'Neptune')
}
BeforeAll {
Import-Module $PSScriptRoot/../Planetarium.psd1 -Force
}
Describe 'Test-PlanetName' -Tag 'Internal' {
It 'Accepts <_>' -ForEach @('Mercury', 'Earth', 'Neptune') {
It 'Accepts <_>' -ForEach $KnownPlanets {
InModuleScope Planetarium -Parameters @{ Name = $_ } {
Test-PlanetName -Name $Name | Should-BeTrue
}
}
It 'Rejects Pluto' {
InModuleScope Planetarium {
Test-PlanetName -Name 'Pluto' | Should-BeFalse
}
}
}
./test.ps1
Describing Test-PlanetName
[+] Accepts Mercury 16ms
[+] Accepts Earth 2ms
[+] Accepts Neptune 2ms
[+] Rejects Pluto 2ms
Identical results — three separately named tests, same as before. What changed is that the list now has a name and a home, so it can grow, be read from a file, or be shared between several It blocks.
That is the everyday use of BeforeDiscovery: generating tests from data. One test per file in a folder, one per row of a fixture, one per supported culture.
BeforeDiscovery is not available inside your testsBeforeDiscovery runs in the discovery pass and It bodies run in the run pass, and variables do not survive the trip. $KnownPlanets is empty if you reference it inside an It.
The way across is the data itself. -ForEach hands each item to the test as $_, and when you pass hashtables their keys arrive as named variables — available in BeforeAll too, not just in It:
Context 'Earth' -ForEach @(@{ Planet = 'Earth'; Order = 3 }) {
BeforeAll { $expected = "$Planet is number $Order" }
It 'knows its order' { $expected | Should-Be 'Earth is number 3' }
}
If you want the full picture of which code runs in which pass, Discovery and run is the page to read — it is the model that explains most surprising Pester behaviour.
Before you move on
0/6Next module: the planet data moves into a file, and your tests get their first real dependency.