Nette Documentation Preview

syntax
Assertion
*********

.[perex]
Assertion використовуються для підтвердження того, що фактичне значення відповідає очікуваному значенню. Це методи класу `Tester\Assert`.

Вибирайте найбільш підходящі assertion. Краще `Assert::same($a, $b)`, ніж `Assert::true($a === $b)`, оскільки при невдачі вона відобразить змістовне повідомлення про помилку. У другому випадку лише `false should be true`, що нічого не говорить нам про вміст змінних `$a` та `$b`.

Більшість assertion також можуть мати необов'язковий опис у параметрі `$description`, який відобразиться в повідомленні про помилку, якщо очікування не виправдається.

Приклади передбачають створений псевдонім:

```php
use Tester\Assert;
```


Assert::same($expected, $actual, ?string $description=null) .[method]
---------------------------------------------------------------------
`$expected` має бути тотожним `$actual`. Те саме, що й PHP оператор `===`.


Assert::notSame($expected, $actual, ?string $description=null) .[method]
------------------------------------------------------------------------
Протилежність `Assert::same()`, тобто те саме, що й PHP оператор `!==`.


Assert::equal($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false) .[method]
-------------------------------------------------------------------------------------------------------------------------
`$expected` має бути однаковим з `$actual`. На відміну від `Assert::same()`, ігнорується ідентичність об'єктів, порядок пар ключ => значення в масивах та незначно відмінні десяткові числа, що можна змінити налаштуванням `$matchIdentity` та `$matchOrder`.

Наступні випадки є однаковими з точки зору `equal()`, але не `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],
);
```

Однак увага, масиви `[1, 2]` та `[2, 1]` не є однаковими, оскільки відрізняється лише порядок значень, а не пар ключ => значення. Масив `[1, 2]` можна записати також як `[0 => 1, 1 => 2]`, і тому однаковим вважатиметься `[1 => 2, 0 => 1]`.

Далі в `$expected` можна використовувати так звані [#očekávání].


Assert::notEqual($expected, $actual, ?string $description=null) .[method]
-------------------------------------------------------------------------
Протилежність `Assert::equal()`.


Assert::contains($needle, string|array $actual, ?string $description=null) .[method]
------------------------------------------------------------------------------------
Якщо `$actual` є рядком, він повинен містити підрядок `$needle`. Якщо це масив, він повинен містити елемент `$needle` (порівнюється строго).


Assert::notContains($needle, string|array $actual, ?string $description=null) .[method]
---------------------------------------------------------------------------------------
Протилежність `Assert::contains()`.


Assert::hasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4}
--------------------------------------------------------------------------------------------------------
`$actual` має бути масивом і повинен містити ключ `$needle`.


Assert::notHasKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4}
-----------------------------------------------------------------------------------------------------------
`$actual` має бути масивом і не повинен містити ключ `$needle`.


Assert::true($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` має бути `true`, тобто `$value === true`.


Assert::truthy($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` має бути істинним, тобто виконає умову `if ($value) ...`.


Assert::false($value, ?string $description=null) .[method]
----------------------------------------------------------
`$value` має бути `false`, тобто `$value === false`.


Assert::falsey($value, ?string $description=null) .[method]
-----------------------------------------------------------
`$value` має бути хибним, тобто виконає умову `if (!$value) ...`.


Assert::null($value, ?string $description=null) .[method]
---------------------------------------------------------
`$value` має бути `null`, тобто `$value === null`.


Assert::notNull($value, ?string $description=null) .[method]
------------------------------------------------------------
`$value` не має бути `null`, тобто `$value !== null`.


Assert::nan($value, ?string $description=null) .[method]
--------------------------------------------------------
`$value` має бути Not a Number. Для тестування значення NAN використовуйте виключно `Assert::nan()`. Значення NAN є дуже специфічним, і assertion `Assert::same()` або `Assert::equal()` можуть працювати неочікувано.


Assert::count($count, Countable|array $value, ?string $description=null) .[method]
----------------------------------------------------------------------------------
Кількість елементів у `$value` має бути `$count`. Тобто те саме, що й `count($value) === $count`.


Assert::type(string|object $type, $value, ?string $description=null) .[method]
------------------------------------------------------------------------------
`$value` має бути даного типу. Як `$type` можемо використати рядок:
- `array`
- `list` - масив, індексований за зростаючим рядом числових ключів від нуля
- `bool`
- `callable`
- `float`
- `int`
- `null`
- `object`
- `resource`
- `scalar`
- `string`
- назва класу або безпосередньо об'єкт, тоді `$value` має бути `instanceof $type`


Assert::exception(callable $callable, string $class, ?string $message=null, $code=null) .[method]
-------------------------------------------------------------------------------------------------
При виклику `$callable` має бути викинуто виняток класу `$class`. Якщо вкажемо `$message`, повідомлення винятку також має [відповідати патерну|#assert-match], а якщо вкажемо `$code`, коди також повинні строго збігатися.

Наступний тест зазнає невдачі, оскільки повідомлення винятку не відповідає:

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

`Assert::exception()` повертає викинутий виняток, тому можна протестувати й вкладений виняток.

```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]
----------------------------------------------------------------------------------------
Перевіряє, чи функція `$callable` згенерувала очікувані помилки (тобто попередження, повідомлення тощо). Як `$type` вкажемо одну з констант `E_...`, тобто, наприклад, `E_WARNING`. А якщо вкажемо `$message`, повідомлення про помилку також має [відповідати патерну|#assert-match]. Наприклад:

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

Якщо callback генерує більше помилок, ми повинні очікувати їх усі в точному порядку. У такому випадку передамо в `$type` масив:

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

.[note]
Якщо як `$type` вказати назву класу, поводиться так само, як `Assert::exception()`.


Assert::noError(callable $callable) .[method]
---------------------------------------------
Перевіряє, чи функція `$callable` не згенерувала жодного попередження, помилки або винятку. Корисно для тестування фрагментів коду, де немає жодного іншого assertion.


Assert::match(string $pattern, $actual, ?string $description=null) .[method]
----------------------------------------------------------------------------
`$actual` має відповідати патерну `$pattern`. Ми можемо використовувати два варіанти патернів: регулярні вирази або placeholder'и/wildcard'и.

Якщо як `$pattern` передамо регулярний вираз, для його розділення ми повинні використовувати `~` або `#`, інші роздільники не підтримуються. Наприклад, тест, коли `$var` має містити лише шістнадцяткові цифри:

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

Другий варіант схожий на звичайне порівняння рядків, але в `$pattern` ми можемо використовувати різні placeholder'и/wildcard'и:

- `%a%` один або більше символів, крім символів кінця рядка
- `%a?%` нуль або більше символів, крім символів кінця рядка
- `%A%` один або більше символів, включно з символами кінця рядка
- `%A?%` нуль або більше символів, включно з символами кінця рядка
- `%s%` один або більше пробілів, крім символів кінця рядка
- `%s?%` нуль або більше пробілів, крім символів кінця рядка
- `%S%` один або більше символів, крім пробілів
- `%S?%` нуль або більше символів, крім пробілів
- `%c%` будь-який один символ, крім символу кінця рядка
- `%d%` одна або більше цифр
- `%d?%` нуль або більше цифр
- `%i%` знакове цілочисельне значення
- `%f%` число з десятковою комою
- `%h%` одна або більше шістнадцяткових цифр
- `%w%` один або більше буквено-цифрових символів
- `%%` символ %

Приклади:

```php
# Знову тест на шістнадцяткове число
Assert::match('%h%', $var);

# Узагальнення шляху до файлу та номера рядка
Assert::match('Error in file %a% on line %i%', $errorMessage);
```


Assert::matchFile(string $file, $actual, ?string $description=null) .[method]
-----------------------------------------------------------------------------
Assertion тотожна [Assert::match() |#assert-match], але патерн завантажується з файлу `$file`. Це корисно для тестування дуже довгих рядків. Файл з тестом залишиться зрозумілим.


Assert::fail(string $message, $actual=null, $expected=null) .[method]
---------------------------------------------------------------------
Ця assertion завжди зазнає невдачі. Іноді це просто корисно. За бажанням можемо вказати й очікуване та фактичне значення.


Очікування
----------
Коли ми хочемо порівняти складніші структури з неконстантними елементами, вищезазначених assertion може бути недостатньо. Наприклад, тестуємо метод, який створює нового користувача і повертає його атрибути як масив. Значення хешу пароля ми не знаємо, але знаємо, що це має бути шістнадцятковий рядок. А про інший елемент знаємо лише, що це має бути об'єкт `DateTime`.

У цих ситуаціях ми можемо використовувати `Tester\Expect` всередині параметра `$expected` методів `Assert::equal()` та `Assert::notEqual()`, за допомогою яких можна легко описати структуру.

```php
use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # очікуємо ціле число
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # очікуємо рядок, що відповідає патерну
	'created_at' => Expect::type(DateTime::class), # очікуємо екземпляр класу
], User::create(123, 'milo', 'RandomPaSsWoRd'));
```

З `Expect` ми можемо виконувати майже ті самі assertion, що й з `Assert`. Тобто нам доступні методи `Expect::same()`, `Expect::match()`, `Expect::count()` тощо. Крім того, їх можна об'єднувати в ланцюжок:

```php
Expect::type(MyIterator::class)->andCount(5);  # очікуємо MyIterator та кількість елементів 5
```

Або можемо писати власні обробники assertion.

```php
Expect::that(function ($value) {
	# повернемо false, якщо очікування не виправдається
});
```


Дослідження помилкових assertion
--------------------------------
Коли assertion зазнає невдачі, Tester виводить, у чому полягає помилка. Якщо порівнюємо складніші структури, Tester створює дампи порівнюваних значень і зберігає їх у директорії `output`. Наприклад, при невдачі вигаданого тесту `Arrays.recursive.phpt` дампи будуть збережені наступним чином:

```
app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # фактичне значення
	│   └── Arrays.recursive.expected  # очікуване значення
	│
	└── Arrays.recursive.phpt          # тест, що зазнав невдачі
```

Назву директорії можемо змінити через `Tester\Dumper::$dumpDir`.

Assertion

Assertion використовуються для підтвердження того, що фактичне значення відповідає очікуваному значенню. Це методи класу Tester\Assert.

Вибирайте найбільш підходящі assertion. Краще Assert::same($a, $b), ніж Assert::true($a === $b), оскільки при невдачі вона відобразить змістовне повідомлення про помилку. У другому випадку лише false should be true, що нічого не говорить нам про вміст змінних $a та $b.

Більшість assertion також можуть мати необов'язковий опис у параметрі $description, який відобразиться в повідомленні про помилку, якщо очікування не виправдається.

Приклади передбачають створений псевдонім:

use Tester\Assert;

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

$expected має бути тотожним $actual. Те саме, що й PHP оператор ===.

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

Протилежність Assert::same(), тобто те саме, що й PHP оператор !==.

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

$expected має бути однаковим з $actual. На відміну від Assert::same(), ігнорується ідентичність об'єктів, порядок пар ключ ⇒ значення в масивах та незначно відмінні десяткові числа, що можна змінити налаштуванням $matchIdentity та $matchOrder.

Наступні випадки є однаковими з точки зору equal(), але не same():

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

Однак увага, масиви [1, 2] та [2, 1] не є однаковими, оскільки відрізняється лише порядок значень, а не пар ключ ⇒ значення. Масив [1, 2] можна записати також як [0 => 1, 1 => 2], і тому однаковим вважатиметься [1 => 2, 0 => 1].

Далі в $expected можна використовувати так звані očekávání.

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

Протилежність Assert::equal().

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

Якщо $actual є рядком, він повинен містити підрядок $needle. Якщо це масив, він повинен містити елемент $needle (порівнюється строго).

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

Протилежність Assert::contains().

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

$actual має бути масивом і повинен містити ключ $needle.

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

$actual має бути масивом і не повинен містити ключ $needle.

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

$value має бути true, тобто $value === true.

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

$value має бути істинним, тобто виконає умову if ($value) ....

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

$value має бути false, тобто $value === false.

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

$value має бути хибним, тобто виконає умову if (!$value) ....

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

$value має бути null, тобто $value === null.

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

$value не має бути null, тобто $value !== null.

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

$value має бути Not a Number. Для тестування значення NAN використовуйте виключно Assert::nan(). Значення NAN є дуже специфічним, і assertion Assert::same() або Assert::equal() можуть працювати неочікувано.

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

Кількість елементів у $value має бути $count. Тобто те саме, що й count($value) === $count.

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

$value має бути даного типу. Як $type можемо використати рядок:

  • array
  • list – масив, індексований за зростаючим рядом числових ключів від нуля
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • назва класу або безпосередньо об'єкт, тоді $value має бути instanceof $type

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

При виклику $callable має бути викинуто виняток класу $class. Якщо вкажемо $message, повідомлення винятку також має відповідати патерну, а якщо вкажемо $code, коди також повинні строго збігатися.

Наступний тест зазнає невдачі, оскільки повідомлення винятку не відповідає:

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

Assert::exception() повертає викинутий виняток, тому можна протестувати й вкладений виняток.

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

Перевіряє, чи функція $callable згенерувала очікувані помилки (тобто попередження, повідомлення тощо). Як $type вкажемо одну з констант E_..., тобто, наприклад, E_WARNING. А якщо вкажемо $message, повідомлення про помилку також має відповідати патерну. Наприклад:

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

Якщо callback генерує більше помилок, ми повинні очікувати їх усі в точному порядку. У такому випадку передамо в $type масив:

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

Якщо як $type вказати назву класу, поводиться так само, як Assert::exception().

Assert::noError(callable $callable)

Перевіряє, чи функція $callable не згенерувала жодного попередження, помилки або винятку. Корисно для тестування фрагментів коду, де немає жодного іншого assertion.

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

$actual має відповідати патерну $pattern. Ми можемо використовувати два варіанти патернів: регулярні вирази або placeholder'и/wildcard'и.

Якщо як $pattern передамо регулярний вираз, для його розділення ми повинні використовувати ~ або #, інші роздільники не підтримуються. Наприклад, тест, коли $var має містити лише шістнадцяткові цифри:

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

Другий варіант схожий на звичайне порівняння рядків, але в $pattern ми можемо використовувати різні placeholder'и/wildcard'и:

  • %a% один або більше символів, крім символів кінця рядка
  • %a?% нуль або більше символів, крім символів кінця рядка
  • %A% один або більше символів, включно з символами кінця рядка
  • %A?% нуль або більше символів, включно з символами кінця рядка
  • %s% один або більше пробілів, крім символів кінця рядка
  • %s?% нуль або більше пробілів, крім символів кінця рядка
  • %S% один або більше символів, крім пробілів
  • %S?% нуль або більше символів, крім пробілів
  • %c% будь-який один символ, крім символу кінця рядка
  • %d% одна або більше цифр
  • %d?% нуль або більше цифр
  • %i% знакове цілочисельне значення
  • %f% число з десятковою комою
  • %h% одна або більше шістнадцяткових цифр
  • %w% один або більше буквено-цифрових символів
  • %% символ %

Приклади:

# Знову тест на шістнадцяткове число
Assert::match('%h%', $var);

# Узагальнення шляху до файлу та номера рядка
Assert::match('Error in file %a% on line %i%', $errorMessage);

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

Assertion тотожна Assert::match(), але патерн завантажується з файлу $file. Це корисно для тестування дуже довгих рядків. Файл з тестом залишиться зрозумілим.

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

Ця assertion завжди зазнає невдачі. Іноді це просто корисно. За бажанням можемо вказати й очікуване та фактичне значення.

Очікування

Коли ми хочемо порівняти складніші структури з неконстантними елементами, вищезазначених assertion може бути недостатньо. Наприклад, тестуємо метод, який створює нового користувача і повертає його атрибути як масив. Значення хешу пароля ми не знаємо, але знаємо, що це має бути шістнадцятковий рядок. А про інший елемент знаємо лише, що це має бути об'єкт DateTime.

У цих ситуаціях ми можемо використовувати Tester\Expect всередині параметра $expected методів Assert::equal() та Assert::notEqual(), за допомогою яких можна легко описати структуру.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # очікуємо ціле число
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # очікуємо рядок, що відповідає патерну
	'created_at' => Expect::type(DateTime::class), # очікуємо екземпляр класу
], User::create(123, 'milo', 'RandomPaSsWoRd'));

З Expect ми можемо виконувати майже ті самі assertion, що й з Assert. Тобто нам доступні методи Expect::same(), Expect::match(), Expect::count() тощо. Крім того, їх можна об'єднувати в ланцюжок:

Expect::type(MyIterator::class)->andCount(5);  # очікуємо MyIterator та кількість елементів 5

Або можемо писати власні обробники assertion.

Expect::that(function ($value) {
	# повернемо false, якщо очікування не виправдається
});

Дослідження помилкових assertion

Коли assertion зазнає невдачі, Tester виводить, у чому полягає помилка. Якщо порівнюємо складніші структури, Tester створює дампи порівнюваних значень і зберігає їх у директорії output. Наприклад, при невдачі вигаданого тесту Arrays.recursive.phpt дампи будуть збережені наступним чином:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # фактичне значення
	│   └── Arrays.recursive.expected  # очікуване значення
	│
	└── Arrays.recursive.phpt          # тест, що зазнав невдачі

Назву директорії можемо змінити через Tester\Dumper::$dumpDir.