Утверждения (Assert)
Утверждения используются для подтверждения того, что
фактическое значение соответствует ожидаемому значению. Это методы
класса 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. Для тестирования значения 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 мы можем
использовать строку:
arraylist– массив, индексированный по возрастающей последовательности числовых ключей от нуляboolcallablefloatintnullobjectresourcescalarstring- имя класса или непосредственно объект, тогда
$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',
);
Если обратный вызов генерирует несколько ошибок, мы должны ожидать
их все в точном порядке. В таком случае передадим в $type массив:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Если в качестве $type вы укажете имя класса, он ведет себя
так же, как Assert::exception().
Assert::noError(callable $callable)
Проверяет, что функция $callable не сгенерировала никаких
предупреждений, ошибок или исключений. Полезно для тестирования
фрагментов кода, где нет других утверждений.
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)
Утверждение идентично 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'), # ожидаем целое число
'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.