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