Asserzioni

Le asserzioni sono utilizzate per affermare che un valore reale corrisponde a un valore atteso. Sono metodi della classe Tester\Assert.

Scegliere le asserzioni più accurate. È meglio Assert::same($a, $b) di Assert::true($a === $b) perché visualizza un messaggio di errore significativo in caso di fallimento. Nel secondo caso si ottiene solo false should be true e non dice nulla sul contenuto delle variabili $a e $b.

La maggior parte delle asserzioni può avere anche un'opzione $description che appare nel messaggio di errore se l'aspettativa fallisce.

Gli esempi assumono che sia definito il seguente alias di classe:

use Tester\Assert;

Assert::same($expected, $actual, string $description=null)

$expected deve essere uguale a $actual. È uguale all'operatore PHP ===.

Assert::notSame($expected, $actual, string $description=null)

Opposto a Assert::same(), quindi uguale all'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(), vengono ignorati l'identità degli oggetti, l'ordine delle coppie chiave ⇒ valore negli array e i numeri decimali marginalmente diversi, che possono essere modificati impostando $matchIdentity e $matchOrder.

I casi seguenti sono identici dal punto di vista di equal(), ma non per same():

Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
	['first' => 11, 'second' => 22],
	['second' => 22, 'first' => 11],
);

Tuttavia, attenzione, l'array [1, 2] e [2, 1] non sono uguali, perché solo l'ordine dei valori differisce, non le coppie chiave ⇒ valore. L'array [1, 2] può anche essere scritto come [0 => 1, 1 => 2] e quindi [1 => 2, 0 => 1] sarà considerato uguale.

È inoltre possibile utilizzare le cosiddette aspettative in $expected.

Assert::notEqual($expected, $actual, string $description=null)

Opposto a Assert::equal().

Assert::contains($needle, string|array $actual, string $description=null)

Se $actual è una stringa, deve contenere la sottostringa $needle. Se è una matrice, deve contenere l'elemento $needle (viene confrontato strettamente).

Assert::notContains($needle, string|array $actual, string $description=null)

Opposto a 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, quindi $value === true.

Assert::truthy($value, string $description=null)

$value deve essere vero, quindi soddisfa la condizione if ($value) ....

Assert::false($value, string $description=null)

$value deve essere false, quindi $value === false.

Assert::falsey($value, string $description=null)

$value deve essere falso, quindi soddisfa la condizione if (!$value) ....

Assert::null($value, string $description=null)

$value deve essere null, quindi $value === null.

Assert::notNull($value, string $description=null)

$value non deve essere null, quindi $value !== null.

Assert::nan($value, string $description=null)

$value deve essere Not a Number. Utilizzare solo Assert::nan() per i test NAN. Il valore NAN è molto specifico e le asserzioni Assert::same() o Assert::equal() possono comportarsi in modo imprevedibile.

Assert::count($count, Countable|array $value, string $description=null)

Il numero di elementi in $value deve essere $count. Quindi lo stesso di count($value) === $count.

Assert::type(string|object $type, $value, string $description=null)

$value deve essere di un determinato tipo. Come $type possiamo usare stringa:

  • array
  • list – array indicizzato in ordine crescente di chiavi numeriche a partire da zero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nome della classe o dell'oggetto direttamente, allora deve passare $value instanceof $type

Assert::exception(callable $callable, string $class, string $message=null, $code=null)

All'invocazione di $callable deve essere lanciata un'eccezione dell'istanza $class. Se si passa $message, il messaggio dell'eccezione deve corrispondere. E se si passa $code, il codice dell'eccezione deve essere lo stesso.

Ad esempio, questo test fallisce 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 un'eccezione lanciata, quindi è possibile testare 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 l'invocazione di $callable generi gli errori previsti (cioè avvisi, notifiche, ecc.). Come $type si specifica una delle costanti E_..., ad esempio E_WARNING. E se si supera $message, anche il messaggio di errore deve corrispondere al modello. Ad esempio:

Assert::error(
	fn() => $i++,
	E_NOTICE,
	'Undefined variable: i',
);

Se il callback genera più errori, dobbiamo aspettarceli tutti nell'ordine esatto. In questo caso, passiamo l'array in $type:

Assert::error(function () {
	$a++;
	$b++;
}, [
	[E_NOTICE, 'Undefined variable: a'],
	[E_NOTICE, 'Undefined variable: b'],
]);

Se $type è un nome di classe, questa asserzione si comporta come Assert::exception().

Assert::noError(callable $callable)

Controlla che la funzione $callable non lanci alcun avviso/nota/errore o eccezione PHP. È utile per testare un pezzo di codice in cui non ci sono altre asserzioni.

Assert::match(string $pattern, $actual, string $description=null)

$actual deve corrispondere a $pattern. Si possono usare due varianti di pattern: espressioni regolari o caratteri jolly.

Se si passa un'espressione regolare come $pattern, si deve usare ~ or # per delimitarla. Altri delimitatori non sono supportati. Ad esempio, il test in cui $var deve contenere solo cifre esadecimali:

Assert::match('#^[0-9a-f]$#i', $var);

L'altra variante è simile al confronto tra stringhe, ma possiamo usare alcuni caratteri jolly in $pattern:

  • %a% uno o più di qualsiasi cosa tranne i caratteri di fine riga
  • %a?% zero o più di qualsiasi cosa ad eccezione dei caratteri di fine riga
  • %A% uno o più di qualsiasi cosa compresi i caratteri di fine riga
  • %A?% zero o più di qualsiasi cosa, compresi i caratteri di fine riga
  • %s% uno o più caratteri di spazio bianco, eccetto i caratteri di fine riga
  • %s?% zero o più caratteri di spazio bianco, eccetto i caratteri di fine riga
  • %S% uno o più caratteri tranne lo spazio bianco
  • %S?% zero o più caratteri ad eccezione dello spazio bianco
  • %c% un singolo carattere di qualsiasi tipo (eccetto la fine della riga)
  • %d% una o più cifre
  • %d?% zero o più cifre
  • %i% valore intero firmato
  • %f% numero in virgola mobile
  • %h% una o più cifre HEX
  • %w% uno o più caratteri alfanumerici
  • %% un carattere %

Esempi:

# Again, hexadecimal number test
Assert::match('%h%', $var);

# Generalized path to file and line number
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 modello viene caricato da $file. È utile per testare stringhe molto lunghe. Il file di test è leggibile.

Assert::fail(string $message, $actual=null, $expected=null)

Questa asserzione fallisce sempre. È solo comoda. Si possono passare facoltativamente i valori attesi e quelli effettivi.

Aspettative

Se si vogliono confrontare strutture più complesse con elementi non costanti, le asserzioni precedenti potrebbero non essere sufficienti. Per esempio, testiamo un metodo che crea un nuovo utente e restituisce i suoi attributi come array. Non conosciamo il valore hash della password, ma sappiamo che deve essere una stringa esadecimale. L'unica cosa che sappiamo dell'elemento successivo è che deve essere un oggetto DateTime.

In questi casi, possiamo usare il parametro Tester\Expect all'interno del parametro $expected dei metodi Assert::equal() e Assert::notEqual(), che possono essere usati per descrivere facilmente la struttura.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # we expect an integer
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # we expect a string matching pattern
	'created_at' => Expect::type(DateTime::class), # we expect an instance of the class
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Con Expect possiamo fare quasi le stesse asserzioni di Assert. Abbiamo quindi metodi come Expect::same(), Expect::match(), Expect::count(), ecc. Inoltre, possiamo concatenarli come:

Expect::type(MyIterator::class)->andCount(5);  # we expect MyIterator and items count is 5

Oppure, possiamo scrivere i nostri gestori di asserzioni.

Expect::that(function ($value) {
	# return false if expectation fails
});

Indagine sulle asserzioni fallite

Il Tester mostra dove si trova l'errore quando un'asserzione fallisce. Quando si confrontano strutture complesse, il Tester crea dei dump dei valori confrontati e li salva nella directory output. Ad esempio, quando il test immaginario Arrays.recursive.phpt fallisce, i dump vengono salvati come segue:

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # actual value
	│   └── Arrays.recursive.expected  # expected value
	│
	└── Arrays.recursive.phpt          # failing test

Possiamo cambiare il nome della directory con Tester\Dumper::$dumpDir.