Затвердження

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