Утверждения
Утверждения служат для подтверждения того, что фактическое
значение соответствует ожидаемому. Это методы класса 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 можно
использовать строку:
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 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.