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. Можем да
използваме два варианта на шаблони: регулярни изрази или
заместващи знаци.
Ако като $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%един или повече буквено-цифрови знаци%%знак %
Примери:
# Отново тест за шестнадесетично число
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.