Skip to content

Testo Property TestingСгенерируй сотни входов. Shrink'айте тот, что ломает.

Property-based testing для PHP 8.3+, плагин для Testo.

Логотип Property Testing

Увидеть падение, потом увидеть 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

На вход пошли четыре сгенерированных аргумента; строка Changed: говорит, что на падение реально влияют только три из них — shrinker нашёл это поиском, вам не пришлось идти в дебаггер, чтобы это увидеть.

Четыре способа увидеть это в коде

php
// Три составляющие по отдельности, без раннера Testo.
$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 к простейшему нечётному int в диапазоне
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
// Сэмплирование генератора напрямую — быстрый способ посмотреть, что он производит.
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()),
    ])];
}

Полные, исполняемые версии всех четырёх — в examples/; что каждый из них показывает — на странице Примеры.