Fixtures that build objects, not rows

A fixture written as SQL or as an array of columns encodes the schema into every test, so a column rename breaks two hundred tests that have nothing to do with it.

final class OrderBuilder
{
    private $lines = array();
    private $status = 'draft';

    public function paid() { $this->status = 'paid'; return $this; }

    public function withLine($sku, $cents)
    {
        $this->lines[] = array( $sku, $cents );
        return $this;
    }

    public function build() { /* ... */ }
}

$order = (new OrderBuilder())->paid()->withLine('FR-100', 4900)->build();

The builder names the thing the test cares about and defaults everything else, so the test reads as a sentence about the scenario rather than a table row. When the schema changes, one file changes. The rule that keeps it useful: a builder method should correspond to a domain concept — paid(), not setStatus() — or it is just a constructor with extra steps.