Assertions (Zusicherungen)

Assertions werden verwendet, um zu bestätigen, dass ein tatsächlicher Wert einem erwarteten Wert entspricht. Dies sind Methoden der Klasse Tester\Assert.

Wählen Sie die am besten geeigneten Assertions. Es ist besser, Assert::same($a, $b) zu verwenden als Assert::true($a === $b), da im Fehlerfall eine aussagekräftige Fehlermeldung angezeigt wird. Im zweiten Fall nur false should be true, was uns nichts über den Inhalt der Variablen $a und $b sagt.

Die meisten Assertions können auch eine optionale Beschreibung im Parameter $description haben, die in der Fehlermeldung angezeigt wird, wenn die Erwartung fehlschlägt.

Die Beispiele setzen voraus, dass ein Alias erstellt wurde:

use Tester\Assert;

Assert::same ($expected, $actual, ?string $description=null)

$expected muss identisch mit $actual sein. Dasselbe wie der PHP-Operator ===.

Assert::notSame ($expected, $actual, ?string $description=null)

Das Gegenteil von Assert::same(), also dasselbe wie der PHP-Operator !==.

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected muss gleich $actual sein. Im Gegensatz zu Assert::same() werden die Identität von Objekten, die Reihenfolge von Schlüssel-Wert-Paaren in Arrays und geringfügig unterschiedliche Dezimalzahlen ignoriert, was durch Setzen von $matchIdentity und $matchOrder geändert werden kann.

Die folgenden Fälle sind aus Sicht von equal() gleich, aber nicht aus Sicht von same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

Aber Vorsicht, die Arrays [1, 2] und [2, 1] sind nicht gleich, da sie sich nur in der Reihenfolge der Werte unterscheiden, nicht der Schlüssel-Wert-Paare. Das Array [1, 2] kann auch als [0 => 1, 1 => 2] geschrieben werden, daher wird [1 => 2, 0 => 1] als gleich betrachtet.

Weiterhin kann in $expected die sogenannte Erwartung (Expectation) verwendet werden.

Assert::notEqual ($expected, $actual, ?string $description=null)

Das Gegenteil von Assert::equal().

Assert::contains ($needle, string|array $actual, ?string $description=null)

Wenn $actual eine Zeichenkette ist, muss sie die Teilzeichenkette $needle enthalten. Wenn es ein Array ist, muss es das Element $needle enthalten (strikt verglichen mit ===).

Assert::notContains ($needle, string|array $actual, ?string $description=null)

Das 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::notHasKey (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 wahrheitsgemäß 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 falsch 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 NAN-Wert ist sehr spezifisch und die Assertions Assert::same() oder Assert::equal() können unerwartet funktionieren.

Assert::count ($count, Countable|array $value, ?string $description=null)

Die Anzahl der Elemente in $value muss $count sein. Also dasselbe wie count($value) === $count.

Assert::type (string|object $type, $value, ?string $description=null)

$value muss vom angegebenen Typ sein. Als $type können wir eine Zeichenkette verwenden:

  • array
  • list – Array, das nach einer aufsteigenden Reihe numerischer Schlüssel ab Null indiziert ist
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • Klassenname oder direkt Objekt, dann muss $value instanceof $type sein

Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)

Beim Aufruf von $callable muss eine Ausnahme der Klasse $class geworfen werden. Wenn wir $message angeben, muss auch die Ausnahmemeldung dem Muster entsprechen, und wenn wir $code angeben, müssen auch die Codes strikt übereinstimmen (===).

Der folgende Test schlägt fehl, da die Ausnahmemeldung nicht übereinstimmt:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is too low', // Erwartete Nachricht stimmt nicht überein
);

Assert::exception() gibt die geworfene Ausnahme zurück, sodass auch eine verschachtelte Ausnahme getestet werden kann.

$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 (callable $callable, int|string|array $type, ?string $message=null)

Überprüft, ob die Funktion $callable die erwarteten PHP-Fehler (d. h. Warnungen, Hinweise usw.) generiert hat. Als $type geben wir eine der E_...-Konstanten an, z. B. E_WARNING. Und wenn wir $message angeben, muss auch die Fehlermeldung dem Muster entsprechen. Zum Beispiel:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

Wenn der Callback mehrere Fehler generiert, müssen wir sie alle in der genauen Reihenfolge erwarten. In diesem Fall übergeben wir in $type ein Array:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

Wenn Sie als $type einen Klassennamen angeben, verhält es sich wie Assert::exception().

Assert::noError (callable $callable)

Überprüft, ob die Funktion $callable keine PHP-Warnung, keinen Fehler oder keine Ausnahme generiert hat. Eignet sich zum Testen von Codeabschnitten, in denen keine weitere Assertion vorhanden ist.

Assert::match (string $pattern, $actual, ?string $description=null)

$actual muss dem Muster $pattern entsprechen. Wir können zwei Varianten von Mustern verwenden: reguläre Ausdrücke oder Platzhalter.

Wenn wir als $pattern einen regulären Ausdruck übergeben, müssen wir ihn mit ~ oder # begrenzen, andere Trennzeichen 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 normalen Zeichenkettenvergleich, aber in $pattern können wir verschiedene Platzhalter verwenden:

  • %a% ein oder mehrere Zeichen, außer Zeilenendezeichen
  • %a?% kein oder mehrere Zeichen, außer Zeilenendezeichen
  • %A% ein oder mehrere Zeichen, einschließlich Zeilenendezeichen
  • %A?% kein oder mehrere Zeichen, einschließlich Zeilenendezeichen
  • %s% ein oder mehrere Leerzeichen, außer Zeilenendezeichen
  • %s?% kein oder mehrere Leerzeichen, außer Zeilenendezeichen
  • %S% ein oder mehrere Zeichen, außer Leerzeichen
  • %S?% kein oder mehrere Zeichen, außer Leerzeichen
  • %c% jedes einzelne Zeichen, außer dem Zeilenendezeichen
  • %d% eine oder mehrere Ziffern
  • %d?% keine oder mehrere Ziffern
  • %i% vorzeichenbehafteter ganzzahliger Wert
  • %f% Fließkommazahl
  • %h% eine oder mehrere hexadezimale Ziffern
  • %w% ein oder mehrere alphanumerische Zeichen
  • %% Zeichen %

Beispiele:

# Erneut Test auf hexadezimale Zahl
Assert::match('%h%', $var);

# Verallgemeinerung des Dateipfads und der Zeilennummer
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile (string $file, $actual, ?string $description=null)

Die Assertion ist identisch mit Assert::match(), aber das Muster wird aus der Datei $file geladen. Dies ist nützlich zum Testen sehr langer Zeichenketten. 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 auch den erwarteten und den tatsächlichen Wert angeben.

Erwartungen

Wenn wir komplexere Strukturen mit nicht-konstanten Elementen vergleichen möchten, reichen die oben genannten Assertions möglicherweise nicht aus. Zum Beispiel testen wir eine Methode, die einen neuen Benutzer erstellt und seine Attribute als Array zurückgibt. Den Wert des Passwort-Hashes kennen wir nicht, aber wir wissen, dass es eine hexadezimale Zeichenkette sein muss. Und über ein weiteres Element wissen wir nur, dass es ein DateTime-Objekt sein muss.

In diesen Situationen können wir Tester\Expect innerhalb des $expected-Parameters der Methoden Assert::equal() und Assert::notEqual() verwenden, mit denen sich die Struktur leicht beschreiben lässt.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # wir erwarten eine ganze Zahl
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # wir erwarten eine Zeichenkette, die dem Muster entspricht
	'created_at' => Expect::type(DateTime::class), # wir erwarten eine Instanz der Klasse
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Mit Expect können wir fast die gleichen Assertions wie mit Assert durchführen. Das heißt, uns stehen Methoden wie 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 5

Oder wir können eigene Assertion-Handler schreiben.

Expect::that(function ($value) {
	# geben false zurück, wenn die Erwartung fehlschlägt
});

Untersuchung fehlgeschlagener Assertions

Wenn eine Assertion fehlschlägt, gibt Tester aus, worin der Fehler besteht. Wenn wir komplexere Strukturen vergleichen, erstellt Tester Dumps der verglichenen Werte und speichert sie im Verzeichnis output. Zum Beispiel werden bei Fehlschlagen des fiktiven Tests Arrays.recursive.phpt die Dumps wie folgt gespeichert:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # tatsächlicher Wert
	│   └── Arrays.recursive.expected  # erwarteter Wert
	│
	└── Arrays.recursive.phpt          # fehlgeschlagener Test

Den Verzeichnisnamen können wir über Tester\Dumper::$dumpDir ändern.