Skip to content

Add Run.Shuffle to shuffle test execution order - #2948

Open
nohwnd wants to merge 2 commits into
pester:mainfrom
nohwnd:random-test-order
Open

Add Run.Shuffle to shuffle test execution order#2948
nohwnd wants to merge 2 commits into
pester:mainfrom
nohwnd:random-test-order

Conversation

@nohwnd

@nohwnd nohwnd commented Jul 31, 2026

Copy link
Copy Markdown
Member

Implements #2425.

Run.Shuffle shuffles the order things run in. It reorders the files, the Describes in a file, the Describes and Contexts in a Describe, and the Its in a block. Items only ever move within their own level.

$config = New-PesterConfiguration
$config.Run.Path = 'tests'
$config.Run.Shuffle = $true
Invoke-Pester -Configuration $config

The order is repeatable. Each run picks a seed and prints it:

Shuffling execution order using seed 1738685315. Set 'Run.ShuffleSeed = 1738685315' to repeat this order.

Set the seed to replay the same order:

$config.Run.ShuffleSeed = 1738685315

You don't have to read it off the screen. The seed that was used is on the result too, so you can grab it after a run and replay:

$r = Invoke-Pester -Configuration $config -PassThru
$r.Configuration.Run.ShuffleSeed.Value   # 1738685315, even when you did not set one

Opting out

Some tests are ordered on purpose, for example mock history that adds up across sibling Its. A file keeps its order with a comment, the same way it opts out of parallel:

# pester:no-shuffle

Describe 'Deploy' {
    It 'builds'  { ... }
    It 'deploys' { ... }
}

Its blocks and tests then run in declaration order even when Run.Shuffle is on. A block level switch like Describe -Ordered { } could follow, keeping that out of this PR for now.

How it works

The seed is resolved once in Invoke-Pester, so it is reported once, shared by the parallel workers, and returned on the result. The files are shuffled in Invoke-Test, and each block's children in PostProcess-DiscoveredBlock, where .Blocks and .Tests are rebuilt from the shuffled order before First/Last are marked, so the one time setup and teardown still wrap the real first and last item.

Tried it on Pester itself

I ran Pester's own tests with Run.Shuffle = $true. It found a few order coupled tests, all the expected kind, mock history that counts calls across sibling Its, and tests that check the exact setup and teardown sequence. Marking tst/functions/Mock.Tests.ps1 with #pester:no-shuffle takes it from 6 failures back to 0 under a shuffled run, so the opt-out does its job.

Tests are in tst/Pester.RSpec.Shuffle.ts.ps1.

Note: the opt-out keeps a file's own order, but the file still moves in the file order shuffle. And in parallel each worker shuffles its own slice of files, so cross file order is not globally reproducible there. The within file shuffle and the seed are.

🤖

nohwnd added 2 commits July 31, 2026 21:34
Adds Run.Random and Run.RandomSeed. When Run.Random is enabled the order
of test files, and the blocks and tests inside them, is shuffled. Items
are only reordered within their own level, so the Describes in a file,
the Describes and Contexts in a Describe, and the Its in a block.

The shuffle uses a seeded System.Random, so a run is repeatable. Set
Run.RandomSeed to repeat a specific order. The default 0 picks a new seed
each run and reports it at the start, so a failing order can be repeated.

Fix pester#2425

🤖
Rename the option to Run.Shuffle / Run.ShuffleSeed. Shuffle reads better than
Random and matches the wording in the run banner.

Add a file level opt-out. A file or script block with a #pester:no-shuffle
comment keeps its blocks and tests in declaration order even when Run.Shuffle
is on, parsed the same way as #pester:no-parallel. Useful for tests that are
order coupled on purpose, like mock history that accumulates across sibling Its.

The resolved seed is written back to the run configuration, so it is returned
on the result (Configuration.Run.ShuffleSeed) even when it was auto picked, and
the run can be repeated on the same commit.

🤖
@nohwnd nohwnd changed the title Add Run.Random to shuffle test execution order Add Run.Shuffle to shuffle test execution order Jul 31, 2026
@nohwnd

nohwnd commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

@fflaten curious what you think about this one, mainly about the opt-out.

Right now a whole file can opt out with #pester:no-shuffle, same style as #pester:no-parallel. But shuffle happens at every level, not just per file, so a file level opt-out feels too coarse on its own. I am thinking a block level switch as a follow up, something like Describe -Ordered { } / Context -Ordered { } to keep just that block's children in declaration order. Does that direction feel right to you, or would you do it differently? Keeping it out of this PR for now.

🤖

@nohwnd
nohwnd requested a review from fflaten July 31, 2026 20:27
t "reorders the Its inside a block" {
# Seeds are chosen so the C block's tests are not in declaration order.
$found = $false
foreach ($seed in 1..20) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a known seed instead of brute force? 🙂


t "reorders the Its inside a nested Context" {
$found = $false
foreach ($seed in 1..20) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

}
'@
foreach ($n in 'Free1', 'Free2', 'Free3') {
Set-Content -Path (Join-Path $folder "$n.Tests.ps1") -Value "Describe '$n' { It 'a' { 1 | Should -Be 1 }; It 'b' { 1 | Should -Be 1 } }"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert that at least one of them was shuffled? As a implementation regression test so a single no-shuffle directive doesn't disable globally.

@fflaten

fflaten commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

@fflaten curious what you think about this one, mainly about the opt-out.

Right now a whole file can opt out with #pester:no-shuffle, same style as #pester:no-parallel. But shuffle happens at every level, not just per file, so a file level opt-out feels too coarse on its own. I am thinking a block level switch as a follow up, something like Describe -Ordered { } / Context -Ordered { } to keep just that block's children in declaration order. Does that direction feel right to you, or would you do it differently? Keeping it out of this PR for now.

🤖

I think we should pick one, not both. Either we support:

  • Block level granularity (with inheritance) using -Ordered/-NoShuffle. For most users could be a single top-level Describe
  • Or container level only. User could split their tests out in a separate container. The feature is opt-in, so requiring a separate container is not unreasonable for highly coupled tests.

Leaning towards block level for flexibility. The directive syntax is new (experimental) and might confuse as we still shuffle the container order - unlike no-parallel which runs it last IIRC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants