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
можна використовувати так звані očekávání.
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
можемо
використати рядок:
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('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
.