Assertok

Az assertok arra szolgálnak, hogy megerősítsék, hogy a tényleges érték megfelel a várt értéknek. Ezek a Tester\Assert osztály metódusai.

Válassza ki a legmegfelelőbb assertokat. Jobb az Assert::same($a, $b), mint az Assert::true($a === $b), mert hiba esetén értelmes hibaüzenetet jelenít meg. A második esetben csak false should be true, ami semmit sem mond nekünk az $a és $b változók tartalmáról.

A legtöbb assertnak lehet egy opcionális leírása is a $description paraméterben, amely a hibaüzenetben jelenik meg, ha az elvárás meghiúsul.

A példák feltételezik egy alias létrehozását:

use Tester\Assert;

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

Az $expected-nek azonosnak kell lennie az $actual-lal. Ugyanaz, mint a PHP === operátora.

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

Az Assert::same() ellentéte, tehát ugyanaz, mint a PHP !== operátora.

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

Az $expected-nek egyenlőnek kell lennie az $actual-lal. Az Assert::same()-től eltérően figyelmen kívül hagyja az objektumok identitását, a kulcs ⇒ érték párok sorrendjét a tömbökben és a marginálisan eltérő tizedes számokat, amit a $matchIdentity és $matchOrder beállításával lehet megváltoztatni.

A következő esetek az equal() szempontjából megegyeznek, de a same() szempontjából nem:

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

Azonban vigyázat, az [1, 2] és [2, 1] tömbök nem egyenlőek, mert csak az értékek sorrendje különbözik, nem a kulcs ⇒ érték pároké. Az [1, 2] tömböt [0 => 1, 1 => 2]-ként is fel lehet írni, ezért az [1 => 2, 0 => 1] fog egyenlőnek számítani.

Továbbá az $expected-ben használhatók az ún. elvárások.

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

Az Assert::equal() ellentéte.

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

Ha az $actual egy string, tartalmaznia kell a $needle részstringet. Ha tömb, tartalmaznia kell a $needle elemet (szigorúan hasonlítva).

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

Az Assert::contains() ellentéte.

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

Az $actual-nak tömbnek kell lennie, és tartalmaznia kell a $needle kulcsot.

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

Az $actual-nak tömbnek kell lennie, és nem tartalmazhatja a $needle kulcsot.

Assert::true ($value, ?string $description=null)

Az $value-nak true-nak kell lennie, tehát $value === true.

Assert::truthy ($value, ?string $description=null)

Az $value-nak igaznak kell lennie (truthy), tehát teljesíti az if ($value) ... feltételt.

Assert::false ($value, ?string $description=null)

Az $value-nak false-nak kell lennie, tehát $value === false.

Assert::falsey ($value, ?string $description=null)

Az $value-nak hamisnak kell lennie (falsey), tehát teljesíti az if (!$value) ... feltételt.

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

Az $value-nak null-nak kell lennie, tehát $value === null.

Assert::notNull ($value, ?string $description=null)

Az $value-nak nem szabad null-nak lennie, tehát $value !== null.

Assert::nan ($value, ?string $description=null)

Az $value-nak Not a Number-nek kell lennie. NAN értékek tesztelésére kizárólag az Assert::nan()-t használja. A NAN érték nagyon specifikus, és az Assert::same() vagy Assert::equal() asszerciók váratlanul működhetnek.

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

Az elemek száma az $value-ban $count kell, hogy legyen. Tehát ugyanaz, mint count($value) === $count.

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

Az $value-nak adott típusúnak kell lennie. $type-ként használhatunk stringet:

  • array
  • list – nullától kezdődő, növekvő sorrendű numerikus kulcsokkal indexelt tömb
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • osztálynév vagy közvetlenül objektum, ekkor az $value instanceof $type kell, hogy legyen

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

A $callable hívásakor a $class osztályú kivételnek kell dobódnia. Ha megadjuk a $message-t, a kivétel üzenetének is meg kell felelnie a mintának, és ha megadjuk a $code-ot, a kódoknak is szigorúan meg kell egyezniük.

A következő teszt meghiúsul, mert a kivétel üzenete nem felel meg:

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is to low',
);

Az Assert::exception() visszaadja a dobott kivételt, így a beágyazott kivételt is tesztelhetjük.

$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)

Ellenőrzi, hogy a $callable függvény generálta-e a várt hibákat (azaz warningokat, notice-okat stb.). $type-ként adjuk meg az E_... konstansok egyikét, például E_WARNING. És ha megadjuk a $message-t, a hibaüzenetnek is meg kell felelnie a mintának. Például:

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

Ha a callback több hibát generál, mindegyiket pontos sorrendben kell várnunk. Ebben az esetben a $type-ban egy tömböt adunk át:

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

Ha $type-ként egy osztálynevet ad meg, ugyanúgy viselkedik, mint az Assert::exception().

Assert::noError (callable $callable)

Ellenőrzi, hogy a $callable függvény nem generált-e semmilyen warningot, hibát vagy kivételt. Hasznos olyan kódrészletek tesztelésére, ahol nincs más assert.

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

Az $actual-nak meg kell felelnie a $pattern mintának. Kétféle mintát használhatunk: reguláris kifejezéseket vagy helyettesítő karaktereket.

Ha $pattern-ként reguláris kifejezést adunk át, annak határolására ~ vagy # kell használni, más elválasztók nem támogatottak. Például egy teszt, ahol a $var-nak csak hexadecimális számjegyeket kell tartalmaznia:

Assert::match('#^[0-9a-f]$#i', $var);

A második változat hasonló a szokásos string összehasonlításhoz, de a $pattern-ben különböző helyettesítő karaktereket használhatunk:

  • %a% egy vagy több karakter, kivéve a sorvégi karaktereket
  • %a?% nulla vagy több karakter, kivéve a sorvégi karaktereket
  • %A% egy vagy több karakter, beleértve a sorvégi karaktereket
  • %A?% nulla vagy több karakter, beleértve a sorvégi karaktereket
  • %s% egy vagy több szóköz karakter, kivéve a sorvégi karaktereket
  • %s?% nulla vagy több szóköz karakter, kivéve a sorvégi karaktereket
  • %S% egy vagy több karakter, kivéve a szóköz karaktereket
  • %S?% nulla vagy több karakter, kivéve a szóköz karaktereket
  • %c% bármilyen egy karakter, kivéve a sorvégi karaktert
  • %d% egy vagy több számjegy
  • %d?% nulla vagy több számjegy
  • %i% előjeles egész szám érték
  • %f% tizedesvesszős szám
  • %h% egy vagy több hexadecimális számjegy
  • %w% egy vagy több alfanumerikus karakter
  • %% a % karakter

Példák:

# Ismét teszt hexadecimális számra
Assert::match('%h%', $var);

# Fájl elérési útjának és sorszámának általánosítása
Assert::match('Error in file %a% on line %i%', $errorMessage);

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

Az assert azonos az Assert::match()-csal, de a minta a $file fájlból töltődik be. Ez hasznos nagyon hosszú stringek tesztelésére. A tesztfájl áttekinthető marad.

Assert::fail (string $message, $actual=null, $expected=null)

Ez az assert mindig meghiúsul. Néha egyszerűen hasznos. Opcionálisan megadhatjuk a várt és a tényleges értéket is.

Elvárások

Ha összetettebb struktúrákat szeretnénk összehasonlítani nem konstans elemekkel, a fenti assertok nem biztos, hogy elegendőek. Például tesztelünk egy metódust, amely új felhasználót hoz létre, és annak attribútumait tömbként adja vissza. A jelszó hash értékét nem ismerjük, de tudjuk, hogy hexadecimális stringnek kell lennie. És egy másik elemről csak annyit tudunk, hogy DateTime objektumnak kell lennie.

Ezekben a helyzetekben használhatjuk a Tester\Expect-et az $expected paraméteren belül az Assert::equal() és Assert::notEqual() metódusokban, amelyek segítségével a struktúra könnyen leírható.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # egész számot várunk
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # mintának megfelelő stringet várunk
	'created_at' => Expect::type(DateTime::class), # osztálypéldányt várunk
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Az Expect-tel szinte ugyanazokat az assertokat végezhetjük el, mint az Assert-tel. Tehát rendelkezésünkre állnak az Expect::same(), Expect::match(), Expect::count() stb. metódusok. Ráadásul láncolhatjuk őket:

Expect::type(MyIterator::class)->andCount(5);  # MyIterator-t és 5 elemet várunk

Vagy írhatunk saját assert kezelőket.

Expect::that(function ($value) {
	# false-t adunk vissza, ha az elvárás meghiúsul
});

Hibás assertok vizsgálata

Ha egy assert meghiúsul, a Tester kiírja, mi a hiba. Ha összetettebb struktúrákat hasonlítunk össze, a Tester dumpokat hoz létre az összehasonlított értékekről, és elmenti őket az output könyvtárba. Például egy képzeletbeli Arrays.recursive.phpt teszt meghiúsulása esetén a dumpok a következőképpen lesznek elmentve:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # tényleges érték
	│   └── Arrays.recursive.expected  # várt érték
	│
	└── Arrays.recursive.phpt          # hibás teszt

A könyvtár nevét a Tester\Dumper::$dumpDir segítségével módosíthatjuk.