Assertions

Les assertions sont utilisées pour confirmer qu'une valeur réelle correspond à une valeur attendue. Ce sont des méthodes de la classe Tester\Assert.

Choisissez les assertions les plus pertinentes. Il est préférable d'utiliser Assert::same($a, $b) plutôt que Assert::true($a === $b), car en cas d'échec, elle affiche un message d'erreur significatif. Dans le second cas, seulement false should be true ce qui ne nous dit rien sur le contenu des variables $a et $b.

La plupart des assertions peuvent également avoir une description facultative dans le paramètre $description, qui sera affichée dans le message d'erreur si l'attente échoue.

Les exemples supposent la création d'un alias :

use Tester\Assert;

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

$expected doit être identique à $actual. C'est la même chose que l'opérateur PHP ===.

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

L'opposé de Assert::same(), donc la même chose que l'opérateur PHP !==.

Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)

$expected doit être égal à $actual. Contrairement à Assert::same(), l'identité des objets, l'ordre des paires clé ⇒ valeur dans les tableaux et les nombres décimaux légèrement différents sont ignorés, ce qui peut être modifié en définissant $matchIdentity et $matchOrder.

Les cas suivants sont considérés comme identiques par equal(), mais pas par same() :

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

Attention cependant, les tableaux [1, 2] et [2, 1] ne sont pas considérés comme identiques, car seul l'ordre des valeurs diffère, pas celui des paires clé ⇒ valeur. Le tableau [1, 2] peut aussi s'écrire [0 => 1, 1 => 2] et sera donc considéré comme identique à [1 => 2, 0 => 1].

De plus, dans $expected, on peut utiliser ce que l'on appelle des attentes.

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

L'opposé de Assert::equal().

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

Si $actual est une chaîne de caractères, elle doit contenir la sous-chaîne $needle. Si c'est un tableau, il doit contenir l'élément $needle (comparaison stricte).

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

L'opposé de Assert::contains().

Assert::hasKey (string|int $needle, array $actual, ?string $description=null)

$actual doit être un tableau et doit contenir la clé $needle.

Assert::notHasKey (string|int $needle, array $actual, ?string $description=null)

$actual doit être un tableau et ne doit pas contenir la clé $needle.

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

$value doit être true, c'est-à-dire $value === true.

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

$value doit être vrai (truthy), c'est-à-dire qu'elle satisfait la condition if ($value) ....

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

$value doit être false, c'est-à-dire $value === false.

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

$value doit être faux (falsey), c'est-à-dire qu'elle satisfait la condition if (!$value) ....

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

$value doit être null, c'est-à-dire $value === null.

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

$value ne doit pas être null, c'est-à-dire $value !== null.

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

$value doit être Not a Number. Pour tester la valeur NAN, utilisez exclusivement Assert::nan(). La valeur NAN est très spécifique et les assertions Assert::same() ou Assert::equal() peuvent fonctionner de manière inattendue.

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

Le nombre d'éléments dans $value doit être $count. C'est donc la même chose que count($value) === $count.

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

$value doit être du type donné. Comme $type, nous pouvons utiliser une chaîne :

  • array
  • list – tableau indexé par une série ascendante de clés numériques à partir de zéro
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nom de classe ou directement un objet, alors $value instanceof $type doit être vrai

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

Lors de l'appel de $callable, une exception de la classe $class doit être levée. Si nous spécifions $message, le message de l'exception doit également correspondre au modèle et si nous spécifions $code, les codes doivent également correspondre strictement.

Le test suivant échouera car le message de l'exception ne correspond pas :

Assert::exception(
	fn() => throw new App\InvalidValueException('Zero value'),
	App\InvalidValueException::class,
	'Value is too low',
);

Assert::exception() retourne l'exception levée, ce qui permet de tester également une exception imbriquée.

$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)

Vérifie que l'appelable $callable a généré les erreurs attendues (c.-à-d. avertissements, notices, etc.). Comme $type, nous indiquons l'une des constantes E_..., par exemple E_WARNING. Et si nous indiquons $message, le message d'erreur doit également correspondre au modèle. Par exemple :

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

Si l'appelable génère plusieurs erreurs, nous devons toutes les attendre dans l'ordre exact. Dans ce cas, nous passons un tableau dans $type :

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

Si vous indiquez un nom de classe comme $type, elle se comporte comme Assert::exception().

Assert::noError (callable $callable)

Vérifie que l'appelable $callable n'a généré aucun avertissement, erreur ou exception. Utile pour tester des fragments de code où aucune autre assertion n'est présente.

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

$actual doit correspondre au pattern $pattern. Nous pouvons utiliser deux variantes de patterns : les expressions régulières ou les caractères génériques.

Si nous passons une expression régulière comme $pattern, nous devons utiliser ~ ou # pour la délimiter, les autres délimiteurs ne sont pas pris en charge. Par exemple, un test où $var ne doit contenir que des chiffres hexadécimaux :

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

La seconde variante est similaire à la comparaison de chaînes classique, mais dans $pattern, nous pouvons utiliser divers caractères génériques :

  • %a% un ou plusieurs caractères, sauf les caractères de fin de ligne
  • %a?% zéro ou plusieurs caractères, sauf les caractères de fin de ligne
  • %A% un ou plusieurs caractères, y compris les caractères de fin de ligne
  • %A?% zéro ou plusieurs caractères, y compris les caractères de fin de ligne
  • %s% un ou plusieurs espaces blancs, sauf les caractères de fin de ligne
  • %s?% zéro ou plusieurs espaces blancs, sauf les caractères de fin de ligne
  • %S% un ou plusieurs caractères, sauf les espaces blancs
  • %S?% zéro ou plusieurs caractères, sauf les espaces blancs
  • %c% n'importe quel caractère unique, sauf le caractère de fin de ligne
  • %d% un ou plusieurs chiffres
  • %d?% zéro ou plusieurs chiffres
  • %i% valeur entière signée
  • %f% nombre à virgule flottante
  • %h% un ou plusieurs chiffres hexadécimaux
  • %w% un ou plusieurs caractères alphanumériques
  • %% le caractère %

Exemples :

# Encore un test pour un nombre hexadécimal
Assert::match('%h%', $var);

# Généralisation du chemin du fichier et du numéro de ligne
Assert::match('Error in file %a% on line %i%', $errorMessage);

Assert::matchFile (string $file, $actual, ?string $description=null)

Cette assertion est identique à Assert::match(), mais le pattern est chargé depuis le fichier $file. C'est utile pour tester des chaînes très longues. Le fichier de test reste clair.

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

Cette assertion échoue systématiquement. C'est parfois utile. Facultativement, nous pouvons également indiquer la valeur attendue et la valeur réelle.

Attentes

Lorsque nous voulons comparer des structures plus complexes contenant des éléments non constants, les assertions ci-dessus peuvent ne pas être suffisantes. Par exemple, nous testons une méthode qui crée un nouvel utilisateur et renvoie ses attributs sous forme de tableau. Nous ne connaissons pas la valeur du hash du mot de passe, mais nous savons qu'il doit s'agir d'une chaîne hexadécimale. Et pour un autre élément, nous savons seulement qu'il doit s'agir d'un objet DateTime.

Dans ces situations, nous pouvons utiliser Tester\Expect à l'intérieur du paramètre $expected des méthodes Assert::equal() et Assert::notEqual(), avec lesquelles la structure peut être facilement décrite.

use Tester\Expect;

Assert::equal([
	'id' => Expect::type('int'),                   # nous attendons un entier
	'username' => 'milo',
	'password' => Expect::match('%h%'),            # nous attendons une chaîne correspondant au pattern
	'created_at' => Expect::type(DateTime::class), # nous attendons une instance de la classe
], User::create(123, 'milo', 'RandomPaSsWoRd'));

Avec Expect, nous pouvons effectuer quasiment les mêmes assertions qu'avec Assert. Ainsi, les méthodes Expect::same(), Expect::match(), Expect::count(), etc. sont à notre disposition. De plus, nous pouvons les chaîner :

Expect::type(MyIterator::class)->andCount(5);  # nous attendons MyIterator et un nombre d'éléments de 5

Ou bien, nous pouvons écrire nos propres gestionnaires d'assertions.

Expect::that(function ($value) {
	# nous retournons false si l'attente échoue
});

Examen des assertions erronées

Lorsqu'une assertion échoue, Tester indique où se situe l'erreur. Si nous comparons des structures plus complexes, Tester crée des dumps des valeurs comparées et les enregistre dans le répertoire output. Par exemple, en cas d'échec du test fictif Arrays.recursive.phpt, les dumps seront enregistrés comme suit :

app/
└── tests/
	├── output/
	│   ├── Arrays.recursive.actual    # valeur actuelle
	│   └── Arrays.recursive.expected  # valeur attendue
	│
	└── Arrays.recursive.phpt          # test échouant

Le nom du répertoire peut être modifié via Tester\Dumper::$dumpDir.