Nette Documentation Preview

syntax
Утверждения
***********

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

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

Большинство утверждений могут также иметь необязательный `$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]` будет считаться равным.

Вы также можете использовать так называемые [ожидания |#Expectations] в `$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. Используйте только `Assert::nan()` для тестирования NAN. Значение NAN очень специфично, и утверждения `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('Нулевое значение'),
	App\InvalidValueException::class,
	'Значение слишком мало',
);
```

Сайт `Assert::exception()` возвращает брошенное исключение, поэтому вы можете проверить вложенное исключение.

```php
$e = Assert::exception(
	fn() => throw new MyException('Что-то не так', 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',
);
```

Если обратный вызов генерирует больше ошибок, мы должны ожидать их все в точном порядке. В этом случае мы передаем массив в `$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` не выбрасывает никаких предупреждений/замечаний/ошибок или исключений PHP. Это полезно для проверки части кода, где нет других утверждений.


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

Если мы передаем регулярное выражение как `$pattern`, мы должны использовать `~` or `#` для его разделения. Другие разделители не поддерживаются. Например, тест, где `$var` должен содержать только шестнадцатеричные цифры:

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

Другой вариант похож на сравнение строк, но мы можем использовать некоторые дикие символы в `$pattern`:

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

Примеры:

```php
# Again, hexadecimal number test
Assert::match('%h%', $var);

# Generalized path to file and line number
Assert::match('Error in file %a% on line %i%', $errorMessage);
```


Assert::matchFile(string $file, $actual, ?string $description=null) .[method]
-----------------------------------------------------------------------------
Утверждение идентично [Assert::match() |#Assert-match], но шаблон загружается из `$file`. Это полезно для тестирования очень длинных строк. Тестовый файл становится читабельным.


Assert::fail(string $message, $actual=null, $expected=null) .[method]
---------------------------------------------------------------------
Это утверждение всегда терпит неудачу. Это просто удобно. По желанию мы можем передавать ожидаемые и фактические значения.


Ожидания .[#toc-expectations]
-----------------------------
Если мы хотим сравнить более сложные структуры с непостоянными элементами, приведенных выше утверждений может быть недостаточно. Например, мы тестируем метод, который создает нового пользователя и возвращает его атрибуты в виде массива. Мы не знаем хэш-значения пароля, но знаем, что он должен быть шестнадцатеричной строкой. А о следующем элементе мы знаем только то, что это должен быть объект `DateTime`.

В этих случаях мы можем использовать `Tester\Expect` внутри параметра `$expected` методов `Assert::equal()` и `Assert::notEqual()`, с помощью которых можно легко описать структуру.

```php
use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # we expect an integer
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # we expect a string matching pattern
	'created_at' => Expect::type(DateTime::class), # we expect an instance of the class
], User::create(123, 'milo', 'RandomPaSsWoRd'));
```

С помощью `Expect` мы можем делать почти те же утверждения, что и с помощью `Assert`. Поэтому у нас есть такие методы, как `Expect::same()`, `Expect::match()`, `Expect::count()` и т.д. Кроме того, мы можем соединить их в цепочку следующим образом:

```php
Expect::type(MyIterator::class)->andCount(5);  # we expect MyIterator and items count is 5
```

Или мы можем написать собственные обработчики утверждений.

```php
Expect::that(function ($value) {
	# return false if expectation fails
});
```


Расследование неудачных утверждений .[#toc-failed-assertions-investigation]
---------------------------------------------------------------------------
Tester показывает, где находится ошибка, когда утверждение терпит неудачу. Когда мы сравниваем сложные структуры, Tester создает дампы сравниваемых значений и сохраняет их в директории `output`. Например, когда воображаемый тест `Arrays.recursive.phpt` терпит неудачу, дампы будут сохранены следующим образом:

```
app/
└── tests/
	├── output/
	│ ├──── Arrays.recursive.actual   # фактическое значение
	│ └──── Arrays.recursive.expected # ожидаемое значение
	│
	└── Arrays.recursive.phpt         # неудачный тест
```

Мы можем изменить имя директории на `Tester\Dumper::$dumpDir`.

Утверждения

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

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

Большинство утверждений могут также иметь необязательный $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. Используйте только Assert::nan() для тестирования NAN. Значение NAN очень специфично, и утверждения 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('Нулевое значение'),
	App\InvalidValueException::class,
	'Значение слишком мало',
);

Сайт Assert::exception() возвращает брошенное исключение, поэтому вы можете проверить вложенное исключение.

$e = Assert::exception(
	fn() => throw new MyException('Что-то не так', 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',
);

Если обратный вызов генерирует больше ошибок, мы должны ожидать их все в точном порядке. В этом случае мы передаем массив в $type:

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

Если $type – имя класса, то это утверждение ведет себя так же, как и Assert::exception().

Assert::noError(callable $callable)

Проверяет, что функция $callable не выбрасывает никаких предупреждений/замечаний/ошибок или исключений PHP. Это полезно для проверки части кода, где нет других утверждений.

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

$actual должен соответствовать $pattern. Мы можем использовать два варианта шаблонов: регулярные выражения или подстановочные знаки.

Если мы передаем регулярное выражение как $pattern, мы должны использовать ~ or # для его разделения. Другие разделители не поддерживаются. Например, тест, где $var должен содержать только шестнадцатеричные цифры:

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

Другой вариант похож на сравнение строк, но мы можем использовать некоторые дикие символы в $pattern:

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

Примеры:

# Again, hexadecimal number test
Assert::match('%h%', $var);

# Generalized path to file and line number
Assert::match('Error in file %a% on line %i%', $errorMessage);

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

Утверждение идентично Assert::match(), но шаблон загружается из $file. Это полезно для тестирования очень длинных строк. Тестовый файл становится читабельным.

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

Это утверждение всегда терпит неудачу. Это просто удобно. По желанию мы можем передавать ожидаемые и фактические значения.

Ожидания

Если мы хотим сравнить более сложные структуры с непостоянными элементами, приведенных выше утверждений может быть недостаточно. Например, мы тестируем метод, который создает нового пользователя и возвращает его атрибуты в виде массива. Мы не знаем хэш-значения пароля, но знаем, что он должен быть шестнадцатеричной строкой. А о следующем элементе мы знаем только то, что это должен быть объект DateTime.

В этих случаях мы можем использовать Tester\Expect внутри параметра $expected методов Assert::equal() и Assert::notEqual(), с помощью которых можно легко описать структуру.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # we expect an integer
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # we expect a string matching pattern
	'created_at' => Expect::type(DateTime::class), # we expect an instance of the class
], User::create(123, 'milo', 'RandomPaSsWoRd'));

С помощью Expect мы можем делать почти те же утверждения, что и с помощью Assert. Поэтому у нас есть такие методы, как Expect::same(), Expect::match(), Expect::count() и т.д. Кроме того, мы можем соединить их в цепочку следующим образом:

Expect::type(MyIterator::class)->andCount(5);  # we expect MyIterator and items count is 5

Или мы можем написать собственные обработчики утверждений.

Expect::that(function ($value) {
	# return false if expectation fails
});

Расследование неудачных утверждений

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

app/
└── tests/
	├── output/
	│ ├──── Arrays.recursive.actual   # фактическое значение
	│ └──── Arrays.recursive.expected # ожидаемое значение
	│
	└── Arrays.recursive.phpt         # неудачный тест

Мы можем изменить имя директории на Tester\Dumper::$dumpDir.