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