Aserce
******
.[perex]
Aserce se používají k potvrzení, že skutečná hodnota odpovídá očekávané hodnotě. Jde o metody třídy `Tester\Assert`.
Vybírejte co nejvhodnější aserce. Je lepší `Assert::same($a, $b)` než `Assert::true($a === $b)`, protože při selhání zobrazí smysluplnou chybovou zprávu. Ve druhém případě pouze `false should be true`, což nám o obsahu proměnných `$a` a `$b` nic neříká.
Když se pole nebo objekty liší hlouběji, než ukáže jejich jednořádkový výpis, doplní zpráva cestu k první odlišné položce: .{data-version:2.7.1}
/--pre .[terminal]
Failed: ['status' => 'ok', 'items' => [...]] should be
... ['status' => 'ok', 'items' => [...]]
at ['items'][1]: 'peach' should be 'pear'
\--
Většina asercí také může mít volitelný popis v parametru `$description`, který se zobrazí v chybové hlášce, pokud očekávání selže.
Příklady předpokládají vytvořený alias:
```php
use Tester\Assert;
```
Assert::same($expected, $actual, ?string $description=null) .[method]
---------------------------------------------------------------------
`$expected` musí být totožný s `$actual`. Totéž jako PHP operátor `===`.
Assert::notSame($expected, $actual, ?string $description=null) .[method]
------------------------------------------------------------------------
Opak `Assert::same()`, tedy totéž jako PHP operátor `!==`.
Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method]
-------------------------------------------------------------------------------------------------------------------------
`$expected` musí být stejný s `$actual`. Na rozdíl od `Assert::same()` se ignoruje identita objektů, pořadí dvojic klíčů => hodnota v polích a marginálně odlišná desetinná čísla, což lze změnit nastavením `$matchIdentity` a `$matchOrder`.
Následující případy jsou shodné z pohledu `equal()`, ale nikoliv `same()`:
```php
Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
['first' => 11, 'second' => 22],
['second' => 22, 'first' => 11],
);
```
Ovšem pozor, pole `[1, 2]` a `[2, 1]` stejné nejsou, protože se liší jen pořadí hodnot, nikoliv dvojic klíč => hodnota. Pole `[1, 2]` lze zapsat také jako `[0 => 1, 1 => 2]` a za stejné se proto bude považovat `[1 => 2, 0 => 1]`.
Dále lze v `$expected` použít tzv. [#očekávání].
Assert::notEqual($expected, $actual, ?string $description=null) .[method]
-------------------------------------------------------------------------
Opak `Assert::equal()`.
Assert::contains($needle, string|array $actual, ?string $description=null) .[method]
------------------------------------------------------------------------------------
Pokud je `$actual` řetězec, musí obsahovat podřetězec `$needle`. Pokud je pole, musí obsahovat prvek `$needle` (porovnává se striktně).
Assert::notContains($needle, string|array $actual, ?string $description=null) .[method]
---------------------------------------------------------------------------------------
Opak `Assert::contains()`.
Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4.0}
----------------------------------------------------------------------------------------------------------
`$actual` musí být pole a musí obsahovat klíč `$needle`.
Assert::hasNotKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4.0}
-------------------------------------------------------------------------------------------------------------
`$actual` musí být pole a nesmí obsahovat klíč `$needle`.
Assert::true($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` musí být `true`, tedy `$value === true`.
Assert::truthy($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` musí být pravdivý, tedy splní podmínku `if ($value) ...`.
Assert::false($value, ?string $description=null) .[method]
----------------------------------------------------------
`$value` musí být `false`, tedy `$value === false`.
Assert::falsey($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` musí být nepravdivý, tedy splní podmínku `if (!$value) ...`.
Assert::null($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` musí být `null`, tedy `$value === null`.
Assert::notNull($value, ?string $description=null) .[method]
------------------------------------------------------------
`$value` nesmí být `null`, tedy `$value !== null`.
Assert::nan($value, ?string $description=null) .[method]
--------------------------------------------------------
`$value` musí být Not a Number. Pro testování NAN hodnoty používejte výhradně `Assert::nan()`. Hodnota NAN je velmi specifická a aserce `Assert::same()` nebo `Assert::equal()` mohou fungovat neočekávaně.
Assert::count($count, Countable|array $value, ?string $description=null) .[method]
----------------------------------------------------------------------------------
Počet prvků ve `$value` musí být `$count`. Tedy totéž jako `count($value) === $count`.
Assert::type(string|object $type, $value, ?string $description=null) .[method]
------------------------------------------------------------------------------
`$value` musí být daného typu. Jako `$type` můžeme použít řetězec:
- `array`
- `list` - pole indexované podle vzestupné řady numerických klíčů od nuly
- `bool`
- `callable`
- `float`
- `int`
- `null`
- `object`
- `resource`
- `scalar`
- `string`
- název třídy nebo přímo objekt, potom musí být `$value instanceof $type`
Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method]
-------------------------------------------------------------------------------------------------
Při zavolání `$callable` musí být vyhozena výjimka třídy `$class`. Pokud uvedeme `$message`, musí [odpovídat vzoru |#Assert::match] i zpráva výjimky a pokud uvedeme `$code`, musí se striktně shodovat i kódy.
Následující test selže, protože neodpovídá zpráva výjimky:
```php
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is too low',
);
```
`Assert::exception()` vrací vyhozenou výjimku, lze tak otestovat i výjimku zahnízděnou.
```php
$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) .[method]
----------------------------------------------------------------------------------------
Kontroluje, že funkce `$callable` vygenerovala očekávané chyby (tj. varování, notices atd.). Jako `$type` uvedeme jednu z konstant `E_...`, tedy například `E_WARNING`. A pokud uvedeme `$message`, musí [odpovídat vzoru |#Assert::match] i chybová zpráva. Například:
```php
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
```
Pokud callback vygeneruje více chyb, musíme je všechny očekávat v přesném pořadí. V takovém případě předáme v `$type` pole:
```php
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
```
.[note]
Pokud jako `$type` uvedete název třídy, chová se stejně jako `Assert::exception()`.
Assert::noError(callable $callable) .[method]
---------------------------------------------
Kontroluje, že funkce `$callable` nevygenerovala žádné varování, chybu nebo výjimku. Hodí se pro testování kousků kódu, kde není žádná další aserce.
Assert::match(string $pattern, $actual, ?string $description=null) .[method]
----------------------------------------------------------------------------
`$actual` musí vyhovět vzoru `$pattern`. Můžeme použít dvě varianty vzorů: regulární výrazy nebo zástupné znaky.
Pokud jako `$pattern` předáme regulární výraz, k jeho ohraničení musíme použít `~` nebo `#`, jiné oddělovače nejsou podporovány. Například test, kdy `$var` musí obsahovat pouze hexadecimální číslice:
```php
Assert::match('#^[0-9a-f]+$#i', $var);
```
Druhá varianta je podobná běžnému porovnání řetězců, ale v `$pattern` můžeme použít různé zástupné znaky:
- `%a%` jeden nebo více znaků, kromě znaků konce řádku
- `%a?%` žádný nebo více znaků, kromě znaků konce řádku
- `%A%` jeden nebo více znaků, včetně znaků konce řádku
- `%A?%` žádný nebo více znaků, včetně znaků konce řádku
- `%s%` jeden nebo více bílých znaků, kromě znaků konce řádku
- `%s?%` žádný nebo více bílých znaků, kromě znaků konce řádku
- `%S%` jeden nebo více znaků, kromě bílých znaků
- `%S?%` žádný nebo více znaků, kromě bílých znaků
- `%c%` jakýkoli jeden znak, kromě znaku konce řádku
- `%d%` jedna nebo více číslic
- `%d?%` žádná nebo více číslic
- `%i%` znaménková celočíselná hodnota
- `%f%` číslo s desetinnou čárkou
- `%h%` jedna nebo více hexadecimálních číslic
- `%w%` jeden nebo více alfanumerických znaků
- `%ds%` oddělovač adresářů (`/` nebo `\`)
- `%%` znak %
Příklady:
```php
# Opět test na hexadecimální číslo
Assert::match('%h%', $var);
# Zobecnění cesty k souboru a čísla řádky
Assert::match('Error in file %a% on line %i%', $errorMessage);
```
Assert::notMatch(string $pattern, $actual, ?string $description=null) .[method]{data-version:2.5.6}
---------------------------------------------------------------------------------------------------
Opak `Assert::match()`.
Assert::matchFile(string $file, $actual, ?string $description=null) .[method]
-----------------------------------------------------------------------------
Aserce je totožná s [#Assert::match()], ale vzor se načítá ze souboru `$file`. To je užitečné pro testování velmi dlouhých řetězců. Soubor s testem zůstane přehledný.
Assert::fail(string $message, $actual=null, $expected=null) .[method]
---------------------------------------------------------------------
Tato aserce vždy selže. Někdy se to prostě hodí. Volitelně můžeme uvést i očekávanou a aktuální hodnotu.
Očekávání
---------
Když chceme porovnat složitější struktury s nekonstantními prvky, nemusí být výše uvedené aserce dostatečné. Například testujeme metodu, která vytváří nového uživatele a vrací jeho atributy jako pole. Hodnotu hashe hesla neznáme, ale víme, že to musí být hexadecimální řetězec. A o dalším prvku víme jen, že to musí být objekt `DateTime`.
V těchto situacích můžeme použít `Tester\Expect` uvnitř `$expected` parametru metod `Assert::equal()` a `Assert::notEqual()`, pomocí kterých lze strukturu snadno popsat.
```php
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), # očekáváme celé číslo
'username' => 'milo',
'password' => Expect::match('%h%'), # očekáváme řetězec vyhovující vzoru
'created_at' => Expect::type(DateTime::class), # očekáváme instanci třídy
], User::create(123, 'milo', 'RandomPaSsWoRd'));
```
S `Expect` můžeme provádět téměř stejné aserce jako s `Assert`. Tedy jsou nám k dispozici metody `Expect::same()`, `Expect::match()`, `Expect::count()` atd. Navíc je můžeme zřetězit:
```php
Expect::type(MyIterator::class)->andCount(5); # očekáváme MyIterator a počet prvků 5
```
Anebo můžeme psát vlastní handlery asercí.
```php
Expect::that(function ($value) {
# vrátíme false, pokud očekávání selže
});
```
Zkoumání chybných asercí
------------------------
Když aserce selže, Tester vypíše, v čem je chyba. Pokud porovnáváme složitější struktury, Tester vytvoří dumpy porovnávaných hodnot a uloží je do adresáře `output`. Například při selhání smyšleného testu `Arrays.recursive.phpt` budou dumpy uloženy následovně:
```
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # aktuální hodnota
│ └── Arrays.recursive.expected # očekávaná hodnota
│
└── Arrays.recursive.phpt # selhávající test
```
Název adresáře můžeme změnit přes `Tester\Dumper::$dumpDir`.
{{maintitle: Aserce v Nette Tester: Assert::same, exception a další}}
{{description: Všechny metody třídy Tester Assert: porovnávání hodnot, kontrola typů a polí, očekávané výjimky a chyby a porovnání textu se vzorem.}}
Aserce
Aserce se používají k potvrzení, že skutečná hodnota odpovídá očekávané hodnotě. Jde o metody
třídy Tester\Assert.
Vybírejte co nejvhodnější aserce. Je lepší Assert::same($a, $b) než Assert::true($a === $b),
protože při selhání zobrazí smysluplnou chybovou zprávu. Ve druhém případě pouze false should be true, což
nám o obsahu proměnných $a a $b nic neříká.
Když se pole nebo objekty liší hlouběji, než ukáže jejich jednořádkový výpis, doplní zpráva
cestu k první odlišné položce:
Failed: ['status' => 'ok', 'items' => [...]] should be
... ['status' => 'ok', 'items' => [...]]
at ['items'][1]: 'peach' should be 'pear'
Většina asercí také může mít volitelný popis v parametru $description, který se zobrazí v chybové
hlášce, pokud očekávání selže.
Příklady předpokládají vytvořený alias:
use Tester\Assert;
Assert::same ($expected, $actual, ?string
$description=null)
$expected musí být totožný s $actual. Totéž jako PHP operátor ===.
Assert::notSame ($expected, $actual, ?string
$description=null)
Opak Assert::same(), tedy totéž jako PHP operátor !==.
Assert::equal ($expected, $actual, ?string
$description=null, bool $matchOrder=false, bool $matchIdentity=false)
$expected musí být stejný s $actual. Na rozdíl od Assert::same() se ignoruje
identita objektů, pořadí dvojic klíčů ⇒ hodnota v polích a marginálně odlišná desetinná čísla, což lze změnit
nastavením $matchIdentity a $matchOrder.
Následující případy jsou shodné z pohledu equal(), ale nikoliv same():
Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
['first' => 11, 'second' => 22],
['second' => 22, 'first' => 11],
);
Ovšem pozor, pole [1, 2] a [2, 1] stejné nejsou, protože se liší jen pořadí hodnot, nikoliv
dvojic klíč ⇒ hodnota. Pole [1, 2] lze zapsat také jako [0 => 1, 1 => 2] a za stejné se
proto bude považovat [1 => 2, 0 => 1].
Dále lze v $expected použít tzv. očekávání.
Assert::notEqual ($expected, $actual, ?string
$description=null)
Opak Assert::equal().
Assert::contains ($needle, string|array
$actual, ?string $description=null)
Pokud je $actual řetězec, musí obsahovat podřetězec $needle. Pokud je pole, musí obsahovat
prvek $needle (porovnává se striktně).
Assert::notContains ($needle, string|array
$actual, ?string $description=null)
Opak Assert::contains().
Assert::hasKey (string|int
$needle, array $actual, ?string $description=null)
$actual musí být pole a musí obsahovat klíč $needle.
Assert::hasNotKey (string|int $needle, array $actual, ?string $description=null)
$actual musí být pole a nesmí obsahovat klíč $needle.
Assert::true ($value, ?string
$description=null)
$value musí být true, tedy $value === true.
Assert::truthy ($value, ?string
$description=null)
$value musí být pravdivý, tedy splní podmínku if ($value) ....
Assert::false ($value, ?string
$description=null)
$value musí být false, tedy $value === false.
Assert::falsey ($value, ?string
$description=null)
$value musí být nepravdivý, tedy splní podmínku if (!$value) ....
Assert::null ($value, ?string
$description=null)
$value musí být null, tedy $value === null.
Assert::notNull ($value, ?string
$description=null)
$value nesmí být null, tedy $value !== null.
Assert::nan ($value, ?string
$description=null)
$value musí být Not a Number. Pro testování NAN hodnoty používejte výhradně Assert::nan().
Hodnota NAN je velmi specifická a aserce Assert::same() nebo Assert::equal() mohou fungovat
neočekávaně.
Assert::count ($count, Countable|array $value,
?string $description=null)
Počet prvků ve $value musí být $count. Tedy totéž jako
count($value) === $count.
Assert::type (string|object $type, $value,
?string $description=null)
$value musí být daného typu. Jako $type můžeme použít řetězec:
array
list – pole indexované podle vzestupné řady numerických klíčů od nuly
bool
callable
float
int
null
object
resource
scalar
string
- název třídy nebo přímo objekt, potom musí být
$value instanceof $type
Assert::exception (callable $callable,
string $class, ?string $message=null, $code=null)
Při zavolání $callable musí být vyhozena výjimka třídy $class. Pokud uvedeme
$message, musí odpovídat vzoru i zpráva výjimky a pokud uvedeme
$code, musí se striktně shodovat i kódy.
Následující test selže, protože neodpovídá zpráva výjimky:
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is too low',
);
Assert::exception() vrací vyhozenou výjimku, lze tak otestovat i výjimku zahnízděnou.
$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)
Kontroluje, že funkce $callable vygenerovala očekávané chyby (tj. varování, notices atd.). Jako
$type uvedeme jednu z konstant E_..., tedy například E_WARNING. A pokud uvedeme
$message, musí odpovídat vzoru i chybová zpráva. Například:
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
Pokud callback vygeneruje více chyb, musíme je všechny očekávat v přesném pořadí. V takovém případě předáme v
$type pole:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Pokud jako $type uvedete název třídy, chová se stejně jako Assert::exception().
Assert::noError (callable
$callable)
Kontroluje, že funkce $callable nevygenerovala žádné varování, chybu nebo výjimku. Hodí se pro testování
kousků kódu, kde není žádná další aserce.
Assert::match (string $pattern, $actual,
?string $description=null)
$actual musí vyhovět vzoru $pattern. Můžeme použít dvě varianty vzorů: regulární výrazy
nebo zástupné znaky.
Pokud jako $pattern předáme regulární výraz, k jeho ohraničení musíme použít ~ nebo
#, jiné oddělovače nejsou podporovány. Například test, kdy $var musí obsahovat pouze
hexadecimální číslice:
Assert::match('#^[0-9a-f]+$#i', $var);
Druhá varianta je podobná běžnému porovnání řetězců, ale v $pattern můžeme použít různé
zástupné znaky:
%a% jeden nebo více znaků, kromě znaků konce řádku
%a?% žádný nebo více znaků, kromě znaků konce řádku
%A% jeden nebo více znaků, včetně znaků konce řádku
%A?% žádný nebo více znaků, včetně znaků konce řádku
%s% jeden nebo více bílých znaků, kromě znaků konce řádku
%s?% žádný nebo více bílých znaků, kromě znaků konce řádku
%S% jeden nebo více znaků, kromě bílých znaků
%S?% žádný nebo více znaků, kromě bílých znaků
%c% jakýkoli jeden znak, kromě znaku konce řádku
%d% jedna nebo více číslic
%d?% žádná nebo více číslic
%i% znaménková celočíselná hodnota
%f% číslo s desetinnou čárkou
%h% jedna nebo více hexadecimálních číslic
%w% jeden nebo více alfanumerických znaků
%ds% oddělovač adresářů (/ nebo \)
%% znak %
Příklady:
# Opět test na hexadecimální číslo
Assert::match('%h%', $var);
# Zobecnění cesty k souboru a čísla řádky
Assert::match('Error in file %a% on line %i%', $errorMessage);
Assert::notMatch (string
$pattern, $actual, ?string $description=null)
Opak Assert::match().
Assert::matchFile (string $file, $actual,
?string $description=null)
Aserce je totožná s Assert::match(), ale vzor se načítá ze souboru $file. To
je užitečné pro testování velmi dlouhých řetězců. Soubor s testem zůstane přehledný.
Assert::fail (string $message, $actual=null,
$expected=null)
Tato aserce vždy selže. Někdy se to prostě hodí. Volitelně můžeme uvést i očekávanou a aktuální hodnotu.
Očekávání
Když chceme porovnat složitější struktury s nekonstantními prvky, nemusí být výše uvedené aserce dostatečné.
Například testujeme metodu, která vytváří nového uživatele a vrací jeho atributy jako pole. Hodnotu hashe hesla neznáme,
ale víme, že to musí být hexadecimální řetězec. A o dalším prvku víme jen, že to musí být objekt
DateTime.
V těchto situacích můžeme použít Tester\Expect uvnitř $expected parametru metod
Assert::equal() a Assert::notEqual(), pomocí kterých lze strukturu snadno popsat.
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), # očekáváme celé číslo
'username' => 'milo',
'password' => Expect::match('%h%'), # očekáváme řetězec vyhovující vzoru
'created_at' => Expect::type(DateTime::class), # očekáváme instanci třídy
], User::create(123, 'milo', 'RandomPaSsWoRd'));
S Expect můžeme provádět téměř stejné aserce jako s Assert. Tedy jsou nám k dispozici
metody Expect::same(), Expect::match(), Expect::count() atd. Navíc je můžeme
zřetězit:
Expect::type(MyIterator::class)->andCount(5); # očekáváme MyIterator a počet prvků 5
Anebo můžeme psát vlastní handlery asercí.
Expect::that(function ($value) {
# vrátíme false, pokud očekávání selže
});
Zkoumání chybných asercí
Když aserce selže, Tester vypíše, v čem je chyba. Pokud porovnáváme složitější struktury, Tester vytvoří dumpy
porovnávaných hodnot a uloží je do adresáře output. Například při selhání smyšleného testu
Arrays.recursive.phpt budou dumpy uloženy následovně:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # aktuální hodnota
│ └── Arrays.recursive.expected # očekávaná hodnota
│
└── Arrays.recursive.phpt # selhávající test
Název adresáře můžeme změnit přes Tester\Dumper::$dumpDir.