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]` поэтому будет считаться таким же.

В `$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.0}
----------------------------------------------------------------------------------------------------------
`$actual` должно быть массивом и должно содержать ключ `$needle`.


Assert::hasNotKey(string|int $needle, array $actual, ?string $description=null) .[method]{data-version:2.4.0}
-------------------------------------------------------------------------------------------------------------
`$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 очень специфично, и утверждения вроде `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 too 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` породила ожидаемые ошибки (то есть warning, notice и т. п.). В качестве `$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` не породила никакого warning, ошибки или исключения. Это удобно для проверки кусков кода, где никакого другого утверждения нет.


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%` один или более буквенно-цифровых символов
- `%ds%` разделитель каталогов (`/` или `\`)
- `%%` один символ %

Примеры:

```php
# Снова проверка шестнадцатеричного числа
Assert::match('%h%', $var);

# Обобщение пути к файлу и номера строки
Assert::match('Error in file %a% on line %i%', $errorMessage);
```


Assert::notMatch(string $pattern, $actual, ?string $description=null) .[method]{data-version:2.5.6}
---------------------------------------------------------------------------------------------------
Противоположность `Assert::match()`.


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


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


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

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

```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` мы можем выполнять почти те же утверждения, что и с `Assert`. То есть нам доступны методы `Expect::same()`, `Expect::match()`, `Expect::count()` и т. д. Кроме того, их можно объединять в цепочку:

```php
Expect::type(MyIterator::class)->andCount(5);  # ожидаем MyIterator и количество элементов 5
```

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

```php
Expect::that(function ($value) {
	# вернём false, если ожидание не оправдалось
});
```


Исследование провалившихся утверждений
--------------------------------------
Когда утверждение проваливается, 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::hasNotKey(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 очень специфично, и утверждения вроде 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 too 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 породила ожидаемые ошибки (то есть warning, notice и т. п.). В качестве $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 не породила никакого warning, ошибки или исключения. Это удобно для проверки кусков кода, где никакого другого утверждения нет.

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% один или более буквенно-цифровых символов
  • %ds% разделитель каталогов (/ или \)
  • %% один символ %

Примеры:

# Снова проверка шестнадцатеричного числа
Assert::match('%h%', $var);

# Обобщение пути к файлу и номера строки
Assert::match('Error in file %a% on line %i%', $errorMessage);

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

Противоположность Assert::match().

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

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

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

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

Ожидания

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

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

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 мы можем выполнять почти те же утверждения, что и с Assert. То есть нам доступны методы Expect::same(), Expect::match(), Expect::count() и т. д. Кроме того, их можно объединять в цепочку:

Expect::type(MyIterator::class)->andCount(5);  # ожидаем MyIterator и количество элементов 5

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

Expect::that(function ($value) {
	# вернём false, если ожидание не оправдалось
});

Исследование провалившихся утверждений

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

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # фактическое значение
	│   └── Arrays.recursive.expected  # ожидаемое значение
	│
	└── Arrays.recursive.phpt          # провалившийся тест

Имя каталога можно изменить через Tester\Dumper::$dumpDir.