Assertion
Assertion використовуються для підтвердження того, що фактичне
значення відповідає очікуваному значенню. Це методи класу
Tester\Assert.
Вибирайте найбільш підходящі assertion. Краще Assert::same($a, $b), ніж
Assert::true($a === $b), оскільки при невдачі вона відобразить змістовне
повідомлення про помилку. У другому випадку лише false should be true, що
нічого не говорить нам про вміст змінних $a та $b.
Більшість assertion також можуть мати необов'язковий опис у параметрі
$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 є дуже специфічним, і assertion
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',
);
Якщо 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 не згенерувала жодного
попередження, помилки або винятку. Корисно для тестування фрагментів
коду, де немає жодного іншого assertion.
Assert::match (string $pattern, $actual, ?string $description=null)
$actual має відповідати патерну $pattern. Ми можемо
використовувати два варіанти патернів: регулярні вирази або
placeholder'и/wildcard'и.
Якщо як $pattern передамо регулярний вираз, для його розділення ми
повинні використовувати ~ або #, інші роздільники не
підтримуються. Наприклад, тест, коли $var має містити лише
шістнадцяткові цифри:
Assert::match('#^[0-9a-f]$#i', $var);
Другий варіант схожий на звичайне порівняння рядків, але в
$pattern ми можемо використовувати різні placeholder'и/wildcard'и:
%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)
Assertion тотожна Assert::match(), але патерн завантажується з
файлу $file. Це корисно для тестування дуже довгих рядків. Файл з
тестом залишиться зрозумілим.
Assert::fail (string $message, $actual=null, $expected=null)
Ця assertion завжди зазнає невдачі. Іноді це просто корисно. За бажанням можемо вказати й очікуване та фактичне значення.
Очікування
Коли ми хочемо порівняти складніші структури з неконстантними
елементами, вищезазначених assertion може бути недостатньо. Наприклад,
тестуємо метод, який створює нового користувача і повертає його
атрибути як масив. Значення хешу пароля ми не знаємо, але знаємо, що це
має бути шістнадцятковий рядок. А про інший елемент знаємо лише, що це
має бути об'єкт 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 ми можемо виконувати майже ті самі assertion, що й з
Assert. Тобто нам доступні методи Expect::same(), Expect::match(),
Expect::count() тощо. Крім того, їх можна об'єднувати в ланцюжок:
Expect::type(MyIterator::class)->andCount(5); # очікуємо MyIterator та кількість елементів 5
Або можемо писати власні обробники assertion.
Expect::that(function ($value) {
# повернемо false, якщо очікування не виправдається
});
Дослідження помилкових assertion
Коли assertion зазнає невдачі, Tester виводить, у чому полягає помилка. Якщо
порівнюємо складніші структури, Tester створює дампи порівнюваних
значень і зберігає їх у директорії output. Наприклад, при невдачі
вигаданого тесту Arrays.recursive.phpt дампи будуть збережені
наступним чином:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # фактичне значення
│ └── Arrays.recursive.expected # очікуване значення
│
└── Arrays.recursive.phpt # тест, що зазнав невдачі
Назву директорії можемо змінити через Tester\Dumper::$dumpDir.