Bei Altsystemen gibt es meist keine Tests – und der Code ist so verflochten, dass man ohne vorherige Änderung auch keine schreiben kann. Die Lösung heißt Characterization Test (auch „Golden Master"): Du testest nicht, was der Code tun soll, sondern nagelst fest, was er heute tatsächlich tut – inklusive aller Macken.
Nimm eine typische gewachsene Preisberechnung:
final class LegacyPriceCalculator
{
public function calculate(array $order): float
{
$net = (float) $order['net'];
$rate = 0.19;
if (($order['country'] ?? 'DE') !== 'DE' && ($order['customerType'] ?? '') === 'b2b') {
$rate = 0.0; // Reverse Charge – innergemeinschaftlich
} elseif (($order['category'] ?? null) === 'book') {
$rate = 0.07; // ermäßigter Satz
}
// gewachsener Sonderfall, an den sich niemand mehr erinnert:
if (($order['net'] ?? 0) > 10000 && ($order['country'] ?? '') === 'DE') {
$net = $net * 0.98; // Großkundenrabatt
}
return round($net * (1 + $rate), 2);
}
}
Du kennst die korrekten Erwartungswerte zunächst gar nicht. Also lässt du die bestehende Methode laufen, liest die Ergebnisse ab und schreibst sie als Erwartung fest:
final class LegacyPriceCalculatorCharacterizationTest extends TestCase
{
/** @dataProvider realWorldCases */
public function test_it_preserves_current_behavior(array $order, float $expected): void
{
self::assertSame($expected, (new LegacyPriceCalculator())->calculate($order));
}
public static function realWorldCases(): array
{
return [
'Standard B2C' => [['net' => 100.0, 'country' => 'DE', 'customerType' => 'b2c'], 119.00],
'B2B Reverse Charge' => [['net' => 100.0, 'country' => 'FR', 'customerType' => 'b2b'], 100.00],
'Buch, ermäßigt' => [['net' => 50.0, 'country' => 'DE', 'category' => 'book'], 53.50],
'Großkunde DE' => [['net' => 20000.0, 'country' => 'DE', 'customerType' => 'b2c'], 23324.00],
];
}
}
Der Großkunden-Sonderfall (> 10000) ist genau die Art von Verhalten, die ein Rewrite versehentlich entsorgen würde – der Test hält ihn fest, bevor jemand die Methode anfasst. Für Funktionen mit vielen Pfaden lohnt Approval Testing (z. B. approvals/approval-tests für PHP): Der erste Lauf wird als genehmigter Snapshot abgelegt, jede Abweichung schlägt automatisch an.