RNG Injection & Reproducibility¶
pytest-stochastic provides built-in RNG (random number generator) management for reproducible stochastic tests.
How RNG Injection Works¶
If your test function's signature includes an rng parameter, the framework automatically injects a seeded numpy.random.Generator:
from pytest_stochastic import stochastic_test
@stochastic_test(expected=0.5, atol=0.05, bounds=(0, 1))
def test_uniform_mean(rng):
return rng.uniform(0, 1)
The framework:
- Creates a
numpy.random.Generatorvianumpy.random.default_rng(seed) - If
seedis specified in the decorator, uses that seed - Otherwise, generates a random seed from
numpy.random.SeedSequence - Passes the same generator to every call of your function within that test run
This means all \(n\) calls share one RNG stream, producing a deterministic sequence for a given seed.
Signature Detection¶
The framework inspects your function's signature using inspect.signature. If it finds a parameter named rng, injection is enabled:
# RNG injected
def test_with_rng(rng):
return rng.normal()
# No RNG injection
def test_without_rng():
import random
return random.random()
You are free to use any randomness source in functions without rng, but reproducibility on failure then depends on your own seed management.
Fixed Seeds¶
Pass seed to the decorator for fully deterministic tests:
@stochastic_test(
expected=0.5, atol=0.05, bounds=(0, 1), seed=42
)
def test_deterministic(rng):
return rng.uniform(0, 1)
This test will produce identical results on every run, on every machine (assuming the same NumPy version).
Seed Reporting on Failure¶
When no fixed seed is specified and a test fails, the failure message includes the seed:
You can reproduce the failure by adding seed=7291038456123 to the decorator.
The stochastic_rng Fixture¶
For tests that don't use the @stochastic_test decorator but still need a seeded RNG, the plugin provides a stochastic_rng pytest fixture:
def test_custom_logic(stochastic_rng):
samples = [stochastic_rng.normal() for _ in range(100)]
assert abs(sum(samples) / len(samples)) < 1.0
The fixture's seed is derived deterministically from the test's node ID (hash(request.node.nodeid) % 2^32), so it is reproducible across runs without specifying a seed.
Best Practices¶
-
Use
rnginjection for all stochastic tests. It ensures reproducibility without manual seed management. -
Don't fix seeds in CI unless you have a specific reason. Random seeds allow your tests to explore different random states across runs, while the concentration inequality guarantees keep the failure rate controlled.
-
Fix seeds for debugging. When a test fails, copy the reported seed into the decorator to reproduce the exact failure.
-
Avoid global RNG state. Functions using
numpy.random.random()(the legacy global RNG) orrandom.random()are not reproducible through the framework. Use the injectedrngparameter instead.