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` може да се използват т.нар. [#очаквания].


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`. Можем да използваме два варианта на шаблони: регулярни изрази или заместващи знаци.

Ако като `$pattern` предадем регулярен израз, за неговото ограничаване трябва да използваме `~` или `#`, други разделители не се поддържат. Например тест, при който `$var` трябва да съдържа само шестнадесетични цифри:

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

Вторият вариант е подобен на обикновеното сравнение на низове, но в `$pattern` можем да използваме различни заместващи знаци:

- `%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()], но шаблонът се зарежда от файла `$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 може да се използват т.нар. очаквания.

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. Можем да използваме два варианта на шаблони: регулярни изрази или заместващи знаци.

Ако като $pattern предадем регулярен израз, за неговото ограничаване трябва да използваме ~ или #, други разделители не се поддържат. Например тест, при който $var трябва да съдържа само шестнадесетични цифри:

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

Вторият вариант е подобен на обикновеното сравнение на низове, но в $pattern можем да използваме различни заместващи знаци:

  • %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.