Assertions
Assertions dienen dazu, zu bestätigen, dass der tatsächliche Wert dem erwarteten Wert entspricht. Es sind
Methoden der Klasse Tester\Assert.
Wählen Sie die jeweils passendste Assertion. Assert::same($a, $b) ist besser als
Assert::true($a === $b), weil sie bei einem Fehlschlag eine aussagekräftige Fehlermeldung anzeigt. Im zweiten Fall
erhalten wir nur false should be true, was uns nichts über den Inhalt der Variablen $a und
$b sagt.
Die meisten Assertions können außerdem eine optionale Beschreibung im Parameter $description haben, die in der
Fehlermeldung angezeigt wird, wenn die Erwartung fehlschlägt.
Die Beispiele setzen voraus, dass folgender Alias angelegt wurde:
use Tester\Assert;
Assert::same ($expected, $actual, ?string $description=null)
$expected muss identisch mit $actual sein. Das entspricht dem PHP-Operator ===.
Assert::notSame ($expected, $actual, ?string $description=null)
Gegenteil von Assert::same(), entspricht also dem PHP-Operator !==.
Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)
$expected muss gleich $actual sein. Anders als bei Assert::same() werden die Identität
von Objekten, die Reihenfolge der Schlüssel-Wert-Paare in Arrays und geringfügig abweichende Dezimalzahlen ignoriert, was sich
über $matchIdentity und $matchOrder ändern lässt.
Die folgenden Fälle sind aus Sicht von equal() gleich, aus Sicht von same() aber nicht:
Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
['first' => 11, 'second' => 22],
['second' => 22, 'first' => 11],
);
Aber Achtung, die Arrays [1, 2] und [2, 1] sind nicht gleich, denn hier unterscheidet sich nur die
Reihenfolge der Werte, nicht die der Schlüssel-Wert-Paare. Das Array [1, 2] lässt sich auch als
[0 => 1, 1 => 2] schreiben, und [1 => 2, 0 => 1] wird deshalb als gleich betrachtet.
In $expected können Sie außerdem sogenannte Expectations verwenden.
Assert::notEqual ($expected, $actual, ?string $description=null)
Gegenteil von Assert::equal().
Assert::contains ($needle, string|array $actual, ?string $description=null)
Ist $actual ein String, muss er den Teilstring $needle enthalten. Ist es ein Array, muss es das
Element $needle enthalten (streng verglichen).
Assert::notContains ($needle, string|array $actual, ?string $description=null)
Gegenteil von Assert::contains().
Assert::hasKey (string|int $needle, array $actual, ?string $description=null)
$actual muss ein Array sein und den Schlüssel $needle enthalten.
Assert::hasNotKey (string|int $needle, array $actual, ?string $description=null)
$actual muss ein Array sein und darf den Schlüssel $needle nicht enthalten.
Assert::true ($value, ?string $description=null)
$value muss true sein, also $value === true.
Assert::truthy ($value, ?string $description=null)
$value muss truthy sein, also die Bedingung if ($value) ... erfüllen.
Assert::false ($value, ?string $description=null)
$value muss false sein, also $value === false.
Assert::falsey ($value, ?string $description=null)
$value muss falsey sein, also die Bedingung if (!$value) ... erfüllen.
Assert::null ($value, ?string $description=null)
$value muss null sein, also $value === null.
Assert::notNull ($value, ?string $description=null)
$value darf nicht null sein, also $value !== null.
Assert::nan ($value, ?string $description=null)
$value muss Not a Number sein. Zum Testen von NAN-Werten verwenden Sie ausschließlich Assert::nan().
Der Wert NAN ist sehr speziell, und Assertions wie Assert::same() oder Assert::equal() können sich
unerwartet verhalten.
Assert::count ($count, Countable|array $value, ?string $description=null)
Die Anzahl der Elemente in $value muss $count sein. Das entspricht
count($value) === $count.
Assert::type (string|object $type, $value, ?string $description=null)
$value muss vom angegebenen Typ sein. Als $type können wir einen String verwenden:
arraylist– ein Array, das nach einer aufsteigenden Reihe numerischer Schlüssel ab null indiziert istboolcallablefloatintnullobjectresourcescalarstring- einen Klassennamen oder direkt ein Objekt, dann muss
$value instanceof $typegelten
Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)
Beim Aufruf von $callable muss eine Exception der Klasse $class geworfen werden. Geben wir
$message an, muss auch die Nachricht der Exception zum Muster passen. Und geben wir
$code an, müssen auch die Codes streng übereinstimmen.
Der folgende Test schlägt zum Beispiel fehl, weil die Nachricht der Exception nicht passt:
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is too low',
);
Assert::exception() gibt die geworfene Exception zurück, sodass Sie auch eine verschachtelte Exception testen
können.
$e = Assert::exception(
fn() => throw new MyException('Something is wrong', 0, new RuntimeException),
MyException::class,
'Something is wrong',
);
Assert::type(RuntimeException::class, $e->getPrevious());
Assert::error (string $callable, int|string|array $type, ?string $message=null)
Prüft, ob die Funktion $callable die erwarteten Fehler erzeugt hat (also Warnings, Notices usw.). Geben Sie als
$type eine der E_...-Konstanten an, zum Beispiel E_WARNING. Und geben wir
$message an, muss auch die Fehlermeldung zum Muster passen. Zum Beispiel:
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
Erzeugt der Callback mehrere Fehler, müssen wir sie alle in der exakten Reihenfolge erwarten. Übergeben Sie in diesem Fall
ein Array in $type:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Wenn Sie als $type einen Klassennamen angeben, verhält sie sich genauso wie
Assert::exception().
Assert::noError (callable $callable)
Prüft, ob die Funktion $callable keine Warnung, keinen Fehler und keine Exception erzeugt hat. Das ist nützlich,
um Codeabschnitte zu testen, in denen es keine andere Assertion gibt.
Assert::match (string $pattern, $actual, ?string $description=null)
$actual muss zum Muster $pattern passen. Wir können zwei Varianten von Mustern verwenden: reguläre
Ausdrücke oder Wildcards.
Übergeben wir als $pattern einen regulären Ausdruck, müssen wir ~ oder # als
Begrenzer verwenden. Andere Begrenzer werden nicht unterstützt. Zum Beispiel ein Test, bei dem $var nur hexadezimale
Ziffern enthalten darf:
Assert::match('#^[0-9a-f]+$#i', $var);
Die zweite Variante ähnelt dem Vergleich gewöhnlicher Strings, aber wir können in $pattern verschiedene
Wildcards verwenden:
%a%ein oder mehr beliebige Zeichen außer Zeilenendezeichen%a?%null oder mehr beliebige Zeichen außer Zeilenendezeichen%A%ein oder mehr beliebige Zeichen einschließlich Zeilenendezeichen%A?%null oder mehr beliebige Zeichen einschließlich Zeilenendezeichen%s%ein oder mehr Whitespace-Zeichen außer Zeilenendezeichen%s?%null oder mehr Whitespace-Zeichen außer Zeilenendezeichen%S%ein oder mehr Zeichen außer Whitespace-Zeichen%S?%null oder mehr Zeichen außer Whitespace-Zeichen%c%ein einzelnes beliebiges Zeichen (außer Zeilenende)%d%eine oder mehr Ziffern%d?%null oder mehr Ziffern%i%ganzzahliger Wert mit Vorzeichen%f%Gleitkommazahl%h%eine oder mehr hexadezimale Ziffern%w%ein oder mehr alphanumerische Zeichen%ds%Verzeichnistrenner (/oder\)%%ein Prozentzeichen
Beispiele:
# Wieder ein Test auf eine hexadezimale Zahl
Assert::match('%h%', $var);
# Verallgemeinerung von Dateipfad und Zeilennummer
Assert::match('Error in file %a% on line %i%', $errorMessage);
Assert::notMatch (string $pattern, $actual, ?string $description=null)
Gegenteil von Assert::match().
Assert::matchFile (string $file, $actual, ?string $description=null)
Diese Assertion ist identisch mit Assert::match(), aber das Muster wird aus der Datei
$file geladen. Das ist beim Testen sehr langer Strings nützlich. Die Testdatei bleibt übersichtlich.
Assert::fail (string $message, $actual=null, $expected=null)
Diese Assertion schlägt immer fehl. Manchmal ist das einfach nützlich. Optional können wir den erwarteten und den tatsächlichen Wert angeben.
Expectations
Wenn wir komplexere Strukturen mit nicht konstanten Elementen vergleichen wollen, reichen die oben genannten Assertions
möglicherweise nicht aus. Nehmen wir zum Beispiel an, wir testen eine Methode, die einen neuen Benutzer anlegt und dessen
Attribute als Array zurückgibt. Den Wert des Passwort-Hashes kennen wir nicht, wir wissen aber, dass es ein hexadezimaler String
sein muss. Und vom nächsten Element wissen wir nur, dass es ein Objekt DateTime sein muss.
In solchen Situationen können wir im Parameter $expected der Methoden Assert::equal() und
Assert::notEqual() Tester\Expect verwenden, womit sich die Struktur leicht beschreiben lässt.
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), # wir erwarten eine Ganzzahl
'username' => 'milo',
'password' => Expect::match('%h%'), # wir erwarten einen String, der zum Muster passt
'created_at' => Expect::type(DateTime::class), # wir erwarten eine Instanz der Klasse
], User::create(123, 'milo', 'RandomPaSsWoRd'));
Mit Expect können wir fast dieselben Assertions durchführen wie mit Assert. Es stehen uns also die
Methoden Expect::same(), Expect::match(), Expect::count() usw. zur Verfügung. Außerdem
können wir sie verketten:
Expect::type(MyIterator::class)->andCount(5); # wir erwarten MyIterator und die Anzahl der Elemente ist 5
Alternativ können wir eigene Handler für Assertions schreiben.
Expect::that(function ($value) {
# false zurückgeben, wenn die Erwartung fehlschlägt
});
Untersuchung fehlgeschlagener Assertions
Wenn eine Assertion fehlschlägt, gibt Tester aus, worin der Fehler besteht. Vergleichen wir komplexe Strukturen, erzeugt
Tester Dumps der verglichenen Werte und speichert sie im Verzeichnis output. Schlägt zum Beispiel der fiktive Test
Arrays.recursive.phpt fehl, werden die Dumps folgendermaßen abgelegt:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # tatsächlicher Wert
│ └── Arrays.recursive.expected # erwarteter Wert
│
└── Arrays.recursive.phpt # fehlgeschlagener Test
Den Namen des Verzeichnisses können wir über Tester\Dumper::$dumpDir ändern.