test-conventions
How tests are written in Iron Pipeline — acceptance tests per scenario, FsCheck properties, seeding GameState directly, the IRandom fixture, golden replays, GdUnit4 scene tests that never click throug
SKILL.md.claude/skills/test-conventions/SKILL.md
GitHubTest conventions (written once, used by every change)
Layers
| Layer | Where | Naming |
|---|---|---|
| Acceptance (one per scenario) | IronPipeline.Sim.Tests/<Area>/<Name>AcceptanceTests.cs |
S<NN>_<Scenario> |
| Property (invariants) | IronPipeline.Sim.Tests/<Area>/<Name>PropertyTests.cs |
<Invariant> e.g. SupplyIsConserved |
| Golden replay | tests/replays/<name>.replay + hash |
run by IronPipeline.Tools replay |
| Scene/UI wiring | IronPipeline.Game/tests/ (GdUnit4, C#) |
<Scene>_<Behavior> |
Seed state, never play to it
Build the exact state a scenario needs directly. Until spec 0004 introduces GameState and the builders (GameStateBuilder, UnitBuilder, MapBuilder under IronPipeline.Sim.Tests/Fixtures/), construct value types and maps inline; once they exist, Givens map 1:1 to builder calls. A test never issues commands just to reach its starting position.
Randomness
Once spec 0006 introduces IRandom, tests inject new SeededRandom(1); a scenario that depends on a roll states the seed in its Given. Never use System.Random in tests.
Oracles are numbers
An acceptance test asserts concrete expected values — a number from the Spec, an exact coordinate, an exact count, identity (a == b) — never a relative claim like "greater than before" or "not null" alone. Property tests assert invariants over generated inputs (FsCheck), bounded to realistic ranges.
Golden replays
A replay is seed + command log → final state hash. Changing a hash on purpose requires a one-line reason in the PR ("Decisions made at the build gate") and the save-and-replay-compat reviewer's sign-off. Never regenerate hashes to make a test pass.
Scene tests (GdUnit4)
- Construct state, hand it to the scene, assert on emitted Commands/events and Sim queries — never on Godot node trees, class names or pixel positions.
- Await signals, never
Task.Delay/ timers. - One scene-test file per Features number so parallel work never edits the same file.
- Feel is not tested here; that's the playtest checklist.
Refactor evidence
Every batch (behavior → test → green) leaves evidence of the refactor step: a refactor: commit, or a no refactor: <reason> line in the batch's commit body. Tests are frozen during the refactor. Reviewers check "refactor evidence present per batch".
Smells that fail review
Sleeps · commanding through setup · asserting on engine internals · an acceptance test with only relative or null-check assertions · tests edited during a refactor step · a [Fact] that duplicates a property · scenario IDs that don't match the Features file · a batch with no refactor evidence.