Asserzioni
Le asserzioni vengono utilizzate per confermare che il valore effettivo corrisponde al valore atteso. Si tratta
di metodi della classe Tester\Assert
.
Scegliete le asserzioni più appropriate. È meglio Assert::same($a, $b)
che Assert::true($a === $b)
,
perché in caso di fallimento visualizza un messaggio di errore significativo. Nel secondo caso, solo
false should be true
che non ci dice nulla sul contenuto delle variabili $a
e $b
.
La maggior parte delle asserzioni può anche avere una descrizione opzionale nel parametro $description
, che viene
visualizzata nel messaggio di errore se l'aspettativa fallisce.
Gli esempi presuppongono la creazione di un alias:
use Tester\Assert;
Assert::same ($expected, $actual, ?string $description=null)
$expected
deve essere identico a $actual
. Lo stesso dell'operatore PHP ===
.
Assert::notSame ($expected, $actual, ?string $description=null)
L'opposto di Assert::same()
, quindi lo stesso dell'operatore PHP !==
.
Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)
$expected
deve essere uguale a $actual
. A differenza di Assert::same()
, si ignora
l'identità degli oggetti, l'ordine delle coppie chiave ⇒ valore negli array e numeri decimali marginalmente diversi, il che
può essere modificato impostando $matchIdentity
e $matchOrder
.
I seguenti casi sono uguali dal punto di vista di equal()
, ma non di same()
:
Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
['first' => 11, 'second' => 22],
['second' => 22, 'first' => 11],
);
Attenzione però, gli array [1, 2]
e [2, 1]
non sono uguali, perché differisce solo l'ordine dei
valori, non delle coppie chiave ⇒ valore. L'array [1, 2]
può essere scritto anche come
[0 => 1, 1 => 2]
e quindi sarà considerato uguale [1 => 2, 0 => 1]
.
Inoltre, in $expected
è possibile utilizzare le cosiddette aspettative.
Assert::notEqual ($expected, $actual, ?string $description=null)
L'opposto di Assert::equal()
.
Assert::contains ($needle, string|array $actual, ?string $description=null)
Se $actual
è una stringa, deve contenere la sottostringa $needle
. Se è un array, deve contenere
l'elemento $needle
(il confronto è rigoroso).
Assert::notContains ($needle, string|array $actual, ?string $description=null)
L'opposto di Assert::contains()
.
Assert::hasKey (string|int $needle, array $actual, ?string $description=null)
$actual
deve essere un array e deve contenere la chiave $needle
.
Assert::notHasKey (string|int $needle, array $actual, ?string $description=null)
$actual
deve essere un array e non deve contenere la chiave $needle
.
Assert::true ($value, ?string $description=null)
$value
deve essere true
, ovvero $value === true
.
Assert::truthy ($value, ?string $description=null)
$value
deve essere truthy, ovvero soddisfa la condizione if ($value) ...
.
Assert::false ($value, ?string $description=null)
$value
deve essere false
, ovvero $value === false
.
Assert::falsey ($value, ?string $description=null)
$value
deve essere falsy, ovvero soddisfa la condizione if (!$value) ...
.
Assert::null ($value, ?string $description=null)
$value
deve essere null
, ovvero $value === null
.
Assert::notNull ($value, ?string $description=null)
$value
non deve essere null
, ovvero $value !== null
.
Assert::nan ($value, ?string $description=null)
$value
deve essere Not a Number. Per testare il valore NAN, utilizzare esclusivamente Assert::nan()
.
Il valore NAN è molto specifico e le asserzioni Assert::same()
o Assert::equal()
possono funzionare in
modo imprevisto.
Assert::count ($count, Countable|array $value, ?string $description=null)
Il numero di elementi in $value
deve essere $count
. Ovvero lo stesso di
count($value) === $count
.
Assert::type (string|object $type, $value, ?string $description=null)
$value
deve essere del tipo specificato. Come $type
possiamo usare una stringa:
array
list
– array indicizzato secondo una serie crescente di chiavi numeriche a partire da zerobool
callable
float
int
null
object
resource
scalar
string
- nome della classe o direttamente l'oggetto, quindi
$value
deve essereinstanceof $type
Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)
Alla chiamata di $callable
deve essere lanciata un'eccezione della classe $class
. Se specifichiamo
$message
, anche il messaggio dell'eccezione deve corrispondere al pattern e se
specifichiamo $code
, anche i codici devono corrispondere rigorosamente.
Il seguente test fallirà perché il messaggio dell'eccezione non corrisponde:
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is to low',
);
Assert::exception()
restituisce l'eccezione lanciata, è quindi possibile testare anche un'eccezione annidata.
$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)
Controlla che la funzione $callable
abbia generato gli errori attesi (cioè warning, notice, ecc.). Come
$type
indichiamo una delle costanti E_...
, ad esempio E_WARNING
. E se specifichiamo
$message
, anche il messaggio di errore deve corrispondere al pattern. Ad esempio:
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
Se il callback genera più errori, dobbiamo aspettarceli tutti nell'ordine esatto. In tal caso, passiamo un array in
$type
:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Se come $type
indicate il nome di una classe, si comporta come Assert::exception()
.
Assert::noError (callable $callable)
Controlla che la funzione $callable
non abbia generato alcun warning, errore o eccezione. È utile per testare
porzioni di codice dove non c'è nessun'altra asserzione.
Assert::match (string $pattern, $actual, ?string $description=null)
$actual
deve corrispondere al pattern $pattern
. Possiamo usare due varianti di pattern: espressioni
regolari o caratteri jolly.
Se come $pattern
passiamo un'espressione regolare, per delimitarla dobbiamo usare ~
o #
,
altri delimitatori non sono supportati. Ad esempio, un test in cui $var
deve contenere solo cifre esadecimali:
Assert::match('#^[0-9a-f]$#i', $var);
La seconda variante è simile al confronto di stringhe comune, ma in $pattern
possiamo usare diversi
caratteri jolly:
%a%
uno o più caratteri, eccetto i caratteri di fine riga%a?%
zero o più caratteri, eccetto i caratteri di fine riga%A%
uno o più caratteri, inclusi i caratteri di fine riga%A?%
zero o più caratteri, inclusi i caratteri di fine riga%s%
uno o più spazi bianchi, eccetto i caratteri di fine riga%s?%
zero o più spazi bianchi, eccetto i caratteri di fine riga%S%
uno o più caratteri, eccetto gli spazi bianchi%S?%
zero o più caratteri, eccetto gli spazi bianchi%c%
qualsiasi carattere singolo, eccetto il carattere di fine riga%d%
una o più cifre%d?%
zero o più cifre%i%
valore intero con segno%f%
numero con virgola mobile%h%
una o più cifre esadecimali%w%
uno o più caratteri alfanumerici%%
il carattere %
Esempi:
# Di nuovo test per numero esadecimale
Assert::match('%h%', $var);
# Generalizzazione del percorso del file e del numero di riga
Assert::match('Error in file %a% on line %i%', $errorMessage);
Assert::matchFile (string $file, $actual, ?string $description=null)
L'asserzione è identica a Assert::match(), ma il pattern viene caricato dal file
$file
. Questo è utile per testare stringhe molto lunghe. Il file con il test rimane leggibile.
Assert::fail (string $message, $actual=null, $expected=null)
Questa asserzione fallisce sempre. A volte è semplicemente utile. Opzionalmente possiamo indicare anche il valore atteso e quello attuale.
Aspettative
Quando vogliamo confrontare strutture più complesse con elementi non costanti, le asserzioni sopra menzionate potrebbero non
essere sufficienti. Ad esempio, testiamo un metodo che crea un nuovo utente e restituisce i suoi attributi come array. Non
conosciamo il valore dell'hash della password, ma sappiamo che deve essere una stringa esadecimale. E di un altro elemento
sappiamo solo che deve essere un oggetto DateTime
.
In queste situazioni possiamo usare Tester\Expect
all'interno del parametro $expected
dei metodi
Assert::equal()
e Assert::notEqual()
, con cui è possibile descrivere facilmente la struttura.
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), // ci aspettiamo un numero intero
'username' => 'milo',
'password' => Expect::match('%h%'), // ci aspettiamo una stringa che corrisponda al pattern
'created_at' => Expect::type(DateTime::class), // ci aspettiamo un'istanza della classe
], User::create(123, 'milo', 'RandomPaSsWoRd'));
Con Expect
possiamo eseguire quasi le stesse asserzioni di Assert
. Quindi abbiamo a disposizione
i metodi Expect::same()
, Expect::match()
, Expect::count()
ecc. Inoltre, possiamo
concatenarli:
Expect::type(MyIterator::class)->andCount(5); // ci aspettiamo MyIterator e un numero di elementi pari a 5
Oppure possiamo scrivere i nostri gestori di asserzioni personalizzati.
Expect::that(function ($value) {
// restituiamo false se l'aspettativa fallisce
});
Esame delle asserzioni errate
Quando un'asserzione fallisce, Tester visualizza dov'è l'errore. Se confrontiamo strutture più complesse, Tester crea dump
dei valori confrontati e li salva nella directory output
. Ad esempio, in caso di fallimento del test fittizio
Arrays.recursive.phpt
, i dump verranno salvati come segue:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # valore attuale
│ └── Arrays.recursive.expected # valore atteso
│
└── Arrays.recursive.phpt # test fallito
Il nome della directory può essere modificato tramite Tester\Dumper::$dumpDir
.