|
| 1 | +Aserce |
| 2 | +****** |
| 3 | + |
| 4 | +.[perex] |
| 5 | +Aserce se používají k potvrzení, že skutečná hodnota odpovídá očekávané hodnotě. Jde o metody třídy `Tester\Assert`. |
| 6 | + |
| 7 | +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á. |
| 8 | + |
| 9 | +Většina assercí také může mít volitelnou popisku v parametru `$description`, která se zobrazí v chybové hlášce, pokud očekávání selže. |
| 10 | + |
| 11 | +Příklady předpokládají vytvořený alias: |
| 12 | + |
| 13 | +```php |
| 14 | +use Tester\Assert; |
| 15 | +``` |
| 16 | + |
| 17 | + |
| 18 | +Assert::same($expected, $actual, string $description = null) .[method] |
| 19 | +---------------------------------------------------------------------- |
| 20 | +`$expected` musí být totožný s `$actual`. To samé jako PHP operátor `===`. |
| 21 | + |
| 22 | + |
| 23 | +Assert::notSame($expected, $actual, string $description = null) .[method] |
| 24 | +------------------------------------------------------------------------- |
| 25 | +Opak `Assert::same()`, tedy to samé jako PHP operátor `!==`. |
| 26 | + |
| 27 | + |
| 28 | +Assert::equal($expected, $actual, string $description = null) .[method] |
| 29 | +----------------------------------------------------------------------- |
| 30 | +`$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. |
| 31 | + |
| 32 | +Následující případy jsou shodné z pohledu `equal()`, ale nikoliv `same()`: |
| 33 | + |
| 34 | +```php |
| 35 | +Assert::equal(0.3, 0.1 + 0.2); |
| 36 | +Assert::equal($obj, clone $obj); |
| 37 | +Assert::equal( |
| 38 | + ['first' => 11, 'second' => 22], |
| 39 | + ['second' => 22, 'first' => 11] |
| 40 | +); |
| 41 | +``` |
| 42 | + |
| 43 | +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]`. |
| 44 | + |
| 45 | +Dále lze v `$expected` použít tzv. [#očekávání]. |
| 46 | + |
| 47 | + |
| 48 | +Assert::notEqual($expected, $actual, string $description = null) .[method] |
| 49 | +-------------------------------------------------------------------------- |
| 50 | +Opak `Assert::equal()`. |
| 51 | + |
| 52 | + |
| 53 | +Assert::contains($needle, string|array $actual, string $description = null) .[method] |
| 54 | +------------------------------------------------------------------------------------- |
| 55 | +Pokud je `$actual` řetězec, musí obsahovat podřetězec `$needle`. Pokud je pole, musí obsahovat prvek `$needle` (porovnává se striktně). |
| 56 | + |
| 57 | + |
| 58 | +Assert::notContains($needle, string|array $actual, string $description = null) .[method] |
| 59 | +---------------------------------------------------------------------------------------- |
| 60 | +Opak `Assert::contains()`. |
| 61 | + |
| 62 | + |
| 63 | +Assert::hasKey(string|int $needle, array $actual, string $description = null) .[method]{data-version:2.4} |
| 64 | +--------------------------------------------------------------------------------------------------------- |
| 65 | +`$actual` musí být pole a musí obsahovat klíč `$needle`. |
| 66 | + |
| 67 | + |
| 68 | +Assert::notHasKey(string|int $needle, array $actual, string $description = null) .[method]{data-version:2.4} |
| 69 | +------------------------------------------------------------------------------------------------------------ |
| 70 | +`$actual` musí být pole a nesmí obsahovat klíč `$needle`. |
| 71 | + |
| 72 | + |
| 73 | +Assert::true($value, string $description = null) .[method] |
| 74 | +---------------------------------------------------------- |
| 75 | +`$value` musí být `true`, tedy `$value === true`. |
| 76 | + |
| 77 | + |
| 78 | +Assert::truthy($value, string $description = null) .[method] |
| 79 | +------------------------------------------------------------ |
| 80 | +`$value` musí být pravdivý, tedy splní podmínku `if ($value) ...`. |
| 81 | + |
| 82 | + |
| 83 | +Assert::false($value, string $description = null) .[method] |
| 84 | +----------------------------------------------------------- |
| 85 | +`$value` musí být `false`, tedy `$value === false`. |
| 86 | + |
| 87 | + |
| 88 | +Assert::falsey($value, string $description = null) .[method] |
| 89 | +------------------------------------------------------------ |
| 90 | +`$value` musí být nepravdivý, tedy splní podmínku `if (!$value) ...`. |
| 91 | + |
| 92 | + |
| 93 | +Assert::null($value, string $description = null) .[method] |
| 94 | +---------------------------------------------------------- |
| 95 | +`$value` musí být `null`, tedy `$value === null`. |
| 96 | + |
| 97 | + |
| 98 | +Assert::notNull($value, string $description = null) .[method] |
| 99 | +------------------------------------------------------------- |
| 100 | +`$value` nesmí být `null`, tedy `$value !== null`. |
| 101 | + |
| 102 | + |
| 103 | +Assert::nan($value, string $description = null) .[method] |
| 104 | +--------------------------------------------------------- |
| 105 | +`$value` musí být Not a Number. Pro testování NAN hodnoty používejte vyhradně `Assert::nan()`. Hodnota NAN je velmi specifická a aserce `Assert::same()` nebo `Assert::equal()` mohou fungovat neočekávaně. |
| 106 | + |
| 107 | + |
| 108 | +Assert::count($count, Countable|array $value, string $description = null) .[method] |
| 109 | +----------------------------------------------------------------------------------- |
| 110 | +Počet prvků ve `$value` musí být `$count`. Tedy to samé jako `count($value) === $count`. |
| 111 | + |
| 112 | + |
| 113 | +Assert::type(string|object $type, $value, string $description = null) .[method] |
| 114 | +------------------------------------------------------------------------------- |
| 115 | +`$value` musí být daného typu. Jako `$type` můžeme použít řetězec: |
| 116 | +- `array` |
| 117 | +- `list` - pole indexované podle vzestupné řady numerických klíčů od nuly |
| 118 | +- `bool` |
| 119 | +- `callable` |
| 120 | +- `float` |
| 121 | +- `int` |
| 122 | +- `null` |
| 123 | +- `object` |
| 124 | +- `resource` |
| 125 | +- `scalar` |
| 126 | +- `string` |
| 127 | +- název třídy nebo přímo objekt, potom musí být `$value instanceof $type` |
| 128 | + |
| 129 | + |
| 130 | +Assert::exception(callable $callable, string $class, string $message = null, $code = null) .[method] |
| 131 | +---------------------------------------------------------------------------------------------------- |
| 132 | +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. |
| 133 | + |
| 134 | +Následující test selže, protože neodpovídá zpráva výjimky: |
| 135 | + |
| 136 | +```php |
| 137 | +Assert::exception(function () { |
| 138 | + throw new App\InvalidValueException('Zero value'); |
| 139 | +}, App\InvalidValueException::class, 'Value is to low'); |
| 140 | +``` |
| 141 | + |
| 142 | +`Assert::exception()` vrací vyhozenou výjimku, lze tak otestovat i výjimku zahnízděnou. |
| 143 | + |
| 144 | +```php |
| 145 | +$e = Assert::exception(function () { |
| 146 | + throw new MyException('Something is wrong', 0, new RuntimeException); |
| 147 | +}, MyException::class, 'Something is wrong'); |
| 148 | + |
| 149 | +Assert::type(RuntimeException::class, $e->getPrevious()); |
| 150 | +``` |
| 151 | + |
| 152 | + |
| 153 | +Assert::error(string $callable, int|string|array $type, string $message = null) .[method] |
| 154 | +----------------------------------------------------------------------------------------- |
| 155 | +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: |
| 156 | + |
| 157 | +```php |
| 158 | +Assert::error(function () { |
| 159 | + $i++; |
| 160 | +}, E_NOTICE, 'Undefined variable: i'); |
| 161 | +``` |
| 162 | + |
| 163 | +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: |
| 164 | + |
| 165 | +```php |
| 166 | +Assert::error(function () { |
| 167 | + $a++; |
| 168 | + $b++; |
| 169 | +}, [ |
| 170 | + [E_NOTICE, 'Undefined variable: a'], |
| 171 | + [E_NOTICE, 'Undefined variable: b'], |
| 172 | +]); |
| 173 | +``` |
| 174 | + |
| 175 | +.[note] |
| 176 | +Pokud jako `$type` uvedete název třídy, chová se stejně jako `Assert::exception()`. |
| 177 | + |
| 178 | + |
| 179 | +Assert::noError(callable $callable) .[method] |
| 180 | +--------------------------------------------- |
| 181 | +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. |
| 182 | + |
| 183 | + |
| 184 | +Assert::match(string $pattern, $actual, string $description = null) .[method] |
| 185 | +----------------------------------------------------------------------------- |
| 186 | +`$actual` musí vyhovět vzoru `$pattern`. Můžeme použít dvě varianty vzorů: regulární výrazy nebo zástupné znaky. |
| 187 | + |
| 188 | +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: |
| 189 | + |
| 190 | +```php |
| 191 | +Assert::match('#^[0-9a-f]$#i', $var); |
| 192 | +``` |
| 193 | + |
| 194 | +Druhá varianta je podobná běžnému porovnání řetězců, ale v `$pattern` můžeme použít různé zástupné znaky: |
| 195 | + |
| 196 | +- `%a%` jeden nebo více znaků, kromě znaků konce řádku |
| 197 | +- `%a?%` žádný nebo více znaků, kromě znaků konce řádku |
| 198 | +- `%A%` jeden nebo více znaků, včetně znaků konce řádku |
| 199 | +- `%A?%` žádný nebo více znaků, včetně znaků konce řádku |
| 200 | +- `%s%` jeden nebo více bílých znaků, kromě znaků konce řádku |
| 201 | +- `%s?%` žádný nebo více bílých znaků, kromě znaků konce řádku |
| 202 | +- `%S%` jeden nebo více znaků, kromě bílých znaků |
| 203 | +- `%S?%` žádný nebo více znaků, kromě bílých znaků |
| 204 | +- `%c%` jakýkoli jeden znak, kromě znaku konce řádku |
| 205 | +- `%d%` jedna nebo více číslic |
| 206 | +- `%d?%` žádná nebo více číslic |
| 207 | +- `%i%` znaménková celočíselná hodnota |
| 208 | +- `%f%` číslo s desetinnou čárkou |
| 209 | +- `%h%` jedna nebo více hexadecimálních číslic |
| 210 | +- `%w%` jeden nebo více alfanumerických znaků |
| 211 | +- `%%` znak % |
| 212 | + |
| 213 | +Příklady: |
| 214 | + |
| 215 | +```php |
| 216 | +# Opět test na hexadecimální číslo |
| 217 | +Assert::match('%h%', $var); |
| 218 | + |
| 219 | +# Zobecnění cesty k souboru a čísla řádky |
| 220 | +Assert::match('Error in file %a% on line %i%', $errorMessage); |
| 221 | +``` |
| 222 | + |
| 223 | + |
| 224 | +Assert::matchFile(string $file, $actual, string $description = null) .[method] |
| 225 | +------------------------------------------------------------------------------ |
| 226 | +Aserce je totožná s [Assert::match() |#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ý. |
| 227 | + |
| 228 | + |
| 229 | +Assert::fail(string $message, $actual = null, $expected = null) .[method] |
| 230 | +------------------------------------------------------------------------- |
| 231 | +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. |
| 232 | + |
| 233 | + |
| 234 | +Očekávání |
| 235 | +--------- |
| 236 | +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, to že musí být hexadecimální řetězec. A o dalším prvku víme jen, že to musí být objekt `DateTime`. |
| 237 | + |
| 238 | +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. |
| 239 | + |
| 240 | +```php |
| 241 | +use Tester\Expect; |
| 242 | + |
| 243 | +Assert::equal([ |
| 244 | + 'id' => Expect::type('int'), # očekáváme celé číslo |
| 245 | + 'username' => 'milo', |
| 246 | + 'password' => Expect::match('%h%'), # očekáváme řetězec vyhovující vzoru |
| 247 | + 'created_at' => Expect::type(DateTime::class), # očekáváme instanci třídy |
| 248 | +], User::create(123, 'milo', 'RandomPaSsWoRd')); |
| 249 | +``` |
| 250 | + |
| 251 | +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: |
| 252 | + |
| 253 | +```php |
| 254 | +Expect::type(MyIterator::class)->andCount(5); # očekáváme MyIterator a počet prvků 5 |
| 255 | +``` |
| 256 | + |
| 257 | +Anebo můžeme psát vlastní handlery asercí. |
| 258 | + |
| 259 | +```php |
| 260 | +Expect::that(function ($value) { |
| 261 | + # vrátíme false, pokud očekávání selže |
| 262 | +}); |
| 263 | +``` |
| 264 | + |
| 265 | + |
| 266 | +Zkoumání chybných asercí |
| 267 | +------------------------ |
| 268 | +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ě: |
| 269 | + |
| 270 | +``` |
| 271 | +app/ |
| 272 | +└── tests/ |
| 273 | + ├── output/ |
| 274 | + │ ├── Arrays.recursive.actual # aktuální hodnota |
| 275 | + │ └── Arrays.recursive.expected # očekávaná hodnota |
| 276 | + │ |
| 277 | + └── Arrays.recursive.phpt # selhávající test |
| 278 | +``` |
| 279 | + |
| 280 | +Název adresáře můžeme změnit přes `Tester\Dumper::$dumpDir`. |
0 commit comments