Aserciones
Las aserciones sirven para confirmar que el valor real coincide con el esperado. Son métodos de la clase
Tester\Assert.
Elija las aserciones más adecuadas. Assert::same($a, $b) es mejor que Assert::true($a === $b),
porque en caso de fallo muestra un mensaje de error con sentido. En el segundo caso solo obtenemos
false should be true, que no nos dice nada sobre el contenido de las variables $a y $b.
La mayoría de las aserciones pueden llevar además una descripción opcional en el parámetro $description, que
se muestra en el mensaje de error si la expectativa falla.
Los ejemplos dan por hecho que se ha creado el siguiente alias:
use Tester\Assert;
Assert::same ($expected, $actual, ?string $description=null)
$expected debe ser idéntico a $actual. Es lo mismo que el operador === de PHP.
Assert::notSame ($expected, $actual, ?string $description=null)
Lo contrario de Assert::same(), es decir, es lo mismo que el operador !== de PHP.
Assert::equal ($expected, $actual, ?string $description=null, bool $matchOrder=false, bool $matchIdentity=false)
$expected debe ser igual a $actual. A diferencia de Assert::same(), se ignoran la
identidad de los objetos, el orden de los pares clave ⇒ valor de los arrays y las diferencias mínimas entre números decimales,
lo que se puede cambiar con $matchIdentity y $matchOrder.
Los siguientes casos son iguales desde la perspectiva de equal(), pero no de same():
Assert::equal(0.3, 0.1 + 0.2);
Assert::equal($obj, clone $obj);
Assert::equal(
['first' => 11, 'second' => 22],
['second' => 22, 'first' => 11],
);
Pero cuidado, los arrays [1, 2] y [2, 1] no son iguales, porque solo difiere el orden de los valores,
no los pares clave ⇒ valor. El array [1, 2] también se puede escribir como [0 => 1, 1 => 2], y
[1 => 2, 0 => 1] se considerará por tanto igual.
En $expected también puede usar las llamadas Expectativas.
Assert::notEqual ($expected, $actual, ?string $description=null)
Lo contrario de Assert::equal().
Assert::contains ($needle, string|array $actual, ?string $description=null)
Si $actual es una cadena, debe contener la subcadena $needle. Si es un array, debe contener el
elemento $needle (comparado de forma estricta).
Assert::notContains ($needle, string|array $actual, ?string $description=null)
Lo contrario de Assert::contains().
Assert::hasKey (string|int $needle, array $actual, ?string $description=null)
$actual debe ser un array y debe contener la clave $needle.
Assert::hasNotKey (string|int $needle, array $actual, ?string $description=null)
$actual debe ser un array y no debe contener la clave $needle.
Assert::true ($value, ?string $description=null)
$value debe ser true, es decir, $value === true.
Assert::truthy ($value, ?string $description=null)
$value debe ser truthy, es decir, cumplir la condición if ($value) ....
Assert::false ($value, ?string $description=null)
$value debe ser false, es decir, $value === false.
Assert::falsey ($value, ?string $description=null)
$value debe ser falsey, es decir, cumplir la condición if (!$value) ....
Assert::null ($value, ?string $description=null)
$value debe ser null, es decir, $value === null.
Assert::notNull ($value, ?string $description=null)
$value no debe ser null, es decir, $value !== null.
Assert::nan ($value, ?string $description=null)
$value debe ser Not a Number. Para probar valores NAN use exclusivamente Assert::nan(). El valor NAN
es muy particular y aserciones como Assert::same() o Assert::equal() pueden comportarse de forma
inesperada.
Assert::count ($count, Countable|array $value, ?string $description=null)
El número de elementos de $value debe ser $count. Es lo mismo que
count($value) === $count.
Assert::type (string|object $type, $value, ?string $description=null)
$value debe ser del tipo dado. Como $type podemos usar una cadena:
arraylist: array indexado según una serie ascendente de claves numéricas desde ceroboolcallablefloatintnullobjectresourcescalarstring- el nombre de una clase, o directamente un objeto, y entonces debe cumplirse
$value instanceof $type
Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)
Al llamar a $callable debe lanzarse una excepción de la clase $class. Si indicamos
$message, el mensaje de la excepción debe además encajar con el patrón. Y si
indicamos $code, los códigos deben coincidir además de forma estricta.
Por ejemplo, la siguiente prueba fallará porque el mensaje de la excepción no coincide:
Assert::exception(
fn() => throw new App\InvalidValueException('Zero value'),
App\InvalidValueException::class,
'Value is too low',
);
Assert::exception() devuelve la excepción lanzada, lo que le permite probar también una excepción anidada.
$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)
Comprueba que la función $callable generó los errores esperados (es decir, warnings, notices, etc.). Como
$type indique una de las constantes E_..., por ejemplo E_WARNING. Y si indicamos
$message, el mensaje de error debe además encajar con el patrón. Por ejemplo:
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
Si el callback genera más errores, debemos esperarlos todos en el orden exacto. En ese caso, pase un array en
$type:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Si indica como $type el nombre de una clase, se comporta igual que
Assert::exception().
Assert::noError (callable $callable)
Comprueba que la función $callable no generó ningún warning, error ni excepción. Es útil para probar
fragmentos de código en los que no hay ninguna otra aserción.
Assert::match (string $pattern, $actual, ?string $description=null)
$actual debe encajar con el patrón $pattern. Podemos usar dos variantes de patrones: expresiones
regulares o comodines.
Si pasamos como $pattern una expresión regular, debemos delimitarla con ~ o #. Otros
delimitadores no están soportados. Por ejemplo, una prueba en la que $var solo puede contener dígitos
hexadecimales:
Assert::match('#^[0-9a-f]+$#i', $var);
La segunda variante es parecida a comparar cadenas corrientes, pero en $pattern podemos usar varios comodines:
%a%uno o más caracteres cualesquiera salvo los de fin de línea%a?%cero o más caracteres cualesquiera salvo los de fin de línea%A%uno o más caracteres cualesquiera, incluidos los de fin de línea%A?%cero o más caracteres cualesquiera, incluidos los de fin de línea%s%uno o más caracteres de espacio en blanco salvo los de fin de línea%s?%cero o más caracteres de espacio en blanco salvo los de fin de línea%S%uno o más caracteres que no sean espacio en blanco%S?%cero o más caracteres que no sean espacio en blanco%c%un único carácter de cualquier tipo (salvo el fin de línea)%d%uno o más dígitos%d?%cero o más dígitos%i%valor entero con signo%f%número en coma flotante%h%uno o más dígitos hexadecimales%w%uno o más caracteres alfanuméricos%ds%separador de directorios (/o\)%%un carácter %
Ejemplos:
# De nuevo, prueba de un número hexadecimal
Assert::match('%h%', $var);
# Generalización de la ruta del archivo y del número de línea
Assert::match('Error in file %a% on line %i%', $errorMessage);
Assert::notMatch (string $pattern, $actual, ?string $description=null)
Lo contrario de Assert::match().
Assert::matchFile (string $file, $actual, ?string $description=null)
Esta aserción es idéntica a Assert::match(), pero el patrón se carga del archivo
$file. Es útil para probar cadenas muy largas. El archivo de prueba se mantiene claro.
Assert::fail (string $message, $actual=null, $expected=null)
Esta aserción falla siempre. A veces simplemente viene bien. Opcionalmente podemos indicar el valor esperado y el real.
Expectativas
Cuando queremos comparar estructuras más complejas con elementos no constantes, las aserciones mencionadas arriba pueden no
bastar. Por ejemplo, estamos probando un método que crea un usuario nuevo y devuelve sus atributos como array. No sabemos el
valor del hash de la contraseña, pero sí que debe ser una cadena hexadecimal. Y del siguiente elemento solo sabemos que debe ser
un objeto DateTime.
En esas situaciones podemos usar Tester\Expect dentro del parámetro $expected de los métodos
Assert::equal() y Assert::notEqual(), con el que la estructura se puede describir con facilidad.
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), # esperamos un entero
'username' => 'milo',
'password' => Expect::match('%h%'), # esperamos una cadena que encaje con el patrón
'created_at' => Expect::type(DateTime::class), # esperamos una instancia de la clase
], User::create(123, 'milo', 'RandomPaSsWoRd'));
Con Expect podemos hacer casi las mismas aserciones que con Assert. Así, tenemos a disposición los
métodos Expect::same(), Expect::match(), Expect::count(), etc. Además, los podemos
encadenar:
Expect::type(MyIterator::class)->andCount(5); # esperamos MyIterator y que el número de elementos sea 5
Alternativamente podemos escribir nuestros propios manejadores de aserciones.
Expect::that(function ($value) {
# devuelve false si la expectativa falla
});
Explorar las aserciones fallidas
Cuando una aserción falla, Tester imprime en qué consiste el error. Si comparamos estructuras complejas, Tester crea volcados
de los valores comparados y los guarda en el directorio output. Por ejemplo, si falla la prueba ficticia
Arrays.recursive.phpt, los volcados se guardarán así:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # valor real
│ └── Arrays.recursive.expected # valor esperado
│
└── Arrays.recursive.phpt # prueba que falla
Podemos cambiar el nombre del directorio con Tester\Dumper::$dumpDir.