Property-based testing, and the input you would not have written

Example-based tests check the cases somebody thought of, and the bugs are in the cases nobody thought of — which is a machine’s job.

public function testMoneyAdditionIsAssociative(): void
{
    $this->forAll(Generatorchoose(0, 1000000),
                  Generatorchoose(0, 1000000),
                  Generatorchoose(0, 1000000))
         ->then(function (int $a, int $b, int $c) {
             $m = fn(int $c) => new Money($c, 'GBP');

             $this->assertEquals(
                 $m($a)->add($m($b))->add($m($c)),
                 $m($a)->add($m($b)->add($m($c)))
             );
         });
}

Finding the property is the hard part and is where the value is: associativity, round-tripping through serialisation, and invariants that must hold for any input are the three that come up. The shrinking is what makes a failure usable — the library reduces a failing input to the smallest one that still fails, which is usually a boundary value. It suits value objects and parsers and is unhelpful for anything with a database in it.