Skip to content

Testo Property TestingGenerate hundreds of inputs. Shrink the one that breaks it.

Property-based testing for PHP 8.3+, built as a plugin for Testo.

Property Testing logo

See it fail, then see it shrink

Property falsified after 246 successful run(s); seed=7382910
  Original: maxAttempts=17, baseSeconds=91, cap=847, attempts=23
  Shrunk:   maxAttempts=1, baseSeconds=848, cap=847, attempts=1 (12 shrink step(s), 41 trial(s))
  Changed:  maxAttempts=17 -> 1, baseSeconds=91 -> 848, attempts=23 -> 1

Four generated arguments went in; the Changed: line tells you only three of them actually drive the failure — the shrinker found that by searching, you didn't have to step through a debugger to see it.

Four ways to see it in code

php
// The three pieces in isolation, no Testo runner involved.
$ints = Gen::intBetween(0, 1000);

$failing = null;
for ($run = 0; $run < 100; ++$run) {
    $shrinkable = $ints->generate($random);

    if ($shrinkable->value % 2 !== 0) {
        $failing = $shrinkable;
        break;
    }
}
// -> shrink toward the simplest odd int in range
php
#[Test]
final class ListReversalProperties
{
    #[Property(runs: 200)]
    public function reversingTwiceRestoresTheList(array $xs): void
    {
        Assert::same(array_reverse(array_reverse($xs)), $xs);
    }

    /** @return array<string, ArbitraryInterface> */
    public static function reversingTwiceRestoresTheListGenerators(): array
    {
        return ['xs' => Gen::arrayOf(Gen::intBetween(-100, 100))];
    }
}
php
// Sample a generator directly — a quick way to eyeball what it produces.
Gen::sample(Gen::intBetween(1, 6), count: 5, seed: 42);
// [3, 1, 6, 6, 2]

Gen::sampleShrinks(Gen::intBetween(0, 100), seed: 1);
// ['value' => 87, 'shrinks' => [0, 44, 66, 77, 82, 85, 86]]
php
#[Property(runs: 200)]
public function stackBehavesLikeItsModel(CommandSequence $sequence): void
{
    StateMachine::check($sequence, static fn(): ExampleStack => new ExampleStack());
}

/** @return array<string, ArbitraryInterface> */
public static function stackBehavesLikeItsModelGenerators(): array
{
    return ['sequence' => Gen::commands([], [
        Gen::map(Gen::intBetween(0, 99), static fn(int $v) => new Push($v)),
        Gen::constant(new Pop()),
    ])];
}

Full, runnable versions of all four live in examples/ — see the Examples page for what each one shows.