Aserciones
Las aserciones se utilizan para confirmar que el valor real corresponde al valor esperado. Son métodos de la
clase Tester\Assert
.
Elige las aserciones más adecuadas. Es mejor Assert::same($a, $b)
que Assert::true($a === $b)
,
porque en caso de fallo muestra un mensaje de error significativo. En el segundo caso, solo false should be true
, lo
que no nos dice nada sobre el contenido de las variables $a
y $b
.
La mayoría de las aserciones también pueden tener una descripción opcional en el parámetro $description
, que
se mostrará en el mensaje de error si la expectativa falla.
Los ejemplos presuponen la creación de un alias:
use Tester\Assert;
Assert::same ($expected, $actual, ?string $description=null)
$expected
debe ser idéntico a $actual
. Lo mismo que el operador PHP ===
.
Assert::notSame ($expected, $actual, ?string $description=null)
Opuesto a Assert::same()
, es decir, lo mismo que el operador 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 ignora la
identidad de los objetos, el orden de los pares clave ⇒ valor en los arrays y los números decimales marginalmente diferentes,
lo que se puede cambiar estableciendo $matchIdentity
y $matchOrder
.
Los siguientes casos son iguales desde el punto de vista 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],
);
Sin embargo, ten cuidado, los arrays [1, 2]
y [2, 1]
no son iguales, porque solo difieren en el orden
de los valores, no en los pares clave ⇒ valor. El array [1, 2]
también se puede escribir como
[0 => 1, 1 => 2]
y, por lo tanto, se considerará igual [1 => 2, 0 => 1]
.
Además, en $expected
se pueden usar las llamadas expectativas.
Assert::notEqual ($expected, $actual, ?string $description=null)
Opuesto a 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
(se compara estrictamente).
Assert::notContains ($needle, string|array $actual, ?string $description=null)
Opuesto a Assert::contains()
.
Assert::hasKey (string|int $needle, array $actual, ?string $description=null)
$actual
debe ser un array y debe contener la clave $needle
.
Assert::notHasKey (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 verdadero (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 falso (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, utiliza exclusivamente Assert::nan()
. El valor
NAN es muy específico y las aserciones Assert::same()
o Assert::equal()
pueden funcionar de manera
inesperada.
Assert::count ($count, Countable|array $value, ?string $description=null)
El número de elementos en $value
debe ser $count
. Es decir, 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:
array
list
– array indexado según una serie ascendente de claves numéricas desde cerobool
callable
float
int
null
object
resource
scalar
string
- nombre de clase o directamente un objeto, entonces
$value
debe serinstanceof $type
Assert::exception (callable $callable, string $class, ?string $message=null, $code=null)
Al llamar a $callable
, se debe lanzar una excepción de la clase $class
. Si especificamos
$message
, el mensaje de la excepción también debe coincidir con el patrón y si
especificamos $code
, los códigos también deben coincidir estrictamente.
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 to low',
);
Assert::exception()
devuelve la excepción lanzada, por lo que también se puede probar 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, advertencias, avisos, etc.). Como
$type
indicaremos una de las constantes E_...
, por ejemplo E_WARNING
. Y si especificamos
$message
, el mensaje de error también debe coincidir con el patrón. Por
ejemplo:
Assert::error(
fn() => $i++,
E_NOTICE,
'Undefined variable: i',
);
Si el callback genera múltiples errores, debemos esperarlos todos en el orden exacto. En tal caso, pasamos un array en
$type
:
Assert::error(function () {
$a++;
$b++;
}, [
[E_NOTICE, 'Undefined variable: a'],
[E_NOTICE, 'Undefined variable: b'],
]);
Si como $type
indicas un nombre de clase, se comporta igual que Assert::exception()
.
Assert::noError (callable $callable)
Comprueba que la función $callable
no generó ninguna advertencia, error o excepción. Es útil para probar
fragmentos de código donde no hay ninguna otra aserción.
Assert::match (string $pattern, $actual, ?string $description=null)
$actual
debe cumplir con el patrón $pattern
. Podemos usar dos variantes de patrones: expresiones
regulares o comodines.
Si como $pattern
pasamos una expresión regular, debemos usar ~
o #
para delimitarla, no
se admiten otros delimitadores. Por ejemplo, una prueba donde $var
debe contener solo dígitos hexadecimales:
Assert::match('#^[0-9a-f]$#i', $var);
La segunda variante es similar a la comparación de cadenas normal, pero en $pattern
podemos usar varios
comodines:
%a%
uno o más caracteres, excepto los caracteres de fin de línea%a?%
cero o más caracteres, excepto los caracteres de fin de línea%A%
uno o más caracteres, incluidos los caracteres de fin de línea%A?%
cero o más caracteres, incluidos los caracteres de fin de línea%s%
uno o más caracteres de espacio en blanco, excepto los caracteres de fin de línea%s?%
cero o más caracteres de espacio en blanco, excepto los caracteres de fin de línea%S%
uno o más caracteres, excepto los caracteres de espacio en blanco%S?%
cero o más caracteres, excepto los caracteres de espacio en blanco%c%
cualquier carácter individual, excepto el carácter de 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 de punto flotante%h%
uno o más dígitos hexadecimales%w%
uno o más caracteres alfanuméricos%%
el carácter %
Ejemplos:
# Nuevamente, prueba para número hexadecimal
Assert::match('%h%', $var);
# Generalización de la ruta del archivo y el número de línea
Assert::match('Error in file %a% on line %i%', $errorMessage);
Assert::matchFile (string $file, $actual, ?string $description=null)
La aserción es idéntica a Assert::match(), pero el patrón se carga desde el archivo
$file
. Esto es útil para probar cadenas muy largas. El archivo con la prueba permanecerá claro.
Assert::fail (string $message, $actual=null, $expected=null)
Esta aserción siempre falla. A veces simplemente es útil. Opcionalmente, también podemos indicar el valor esperado y el actual.
Expectativas
Cuando queremos comparar estructuras más complejas con elementos no constantes, las aserciones anteriores pueden no ser
suficientes. Por ejemplo, probamos un método que crea un nuevo usuario y devuelve sus atributos como un array. No conocemos el
valor del hash de la contraseña, pero sabemos que debe ser una cadena hexadecimal. Y sobre otro elemento solo sabemos que debe
ser un objeto DateTime
.
En estas situaciones, podemos usar Tester\Expect
dentro del parámetro $expected
de los métodos
Assert::equal()
y Assert::notEqual()
, con los que se puede describir fácilmente la estructura.
use Tester\Expect;
Assert::equal([
'id' => Expect::type('int'), # esperamos un número entero
'username' => 'milo',
'password' => Expect::match('%h%'), # esperamos una cadena que coincida con el patrón
'created_at' => Expect::type(DateTime::class), # esperamos una instancia de la clase
], User::create(123, 'milo', 'RandomPaSsWoRd'));
Con Expect
podemos realizar casi las mismas aserciones que con Assert
. Es decir, tenemos a nuestra
disposición los métodos Expect::same()
, Expect::match()
, Expect::count()
, etc. Además,
podemos encadenarlos:
Expect::type(MyIterator::class)->andCount(5); # esperamos MyIterator y un número de elementos de 5
O podemos escribir nuestros propios manejadores de aserciones.
Expect::that(function ($value) {
# devolvemos false si la expectativa falla
});
Examinando aserciones erróneas
Cuando una aserción falla, Tester imprime cuál es el error. Si comparamos estructuras más 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 de la siguiente manera:
app/
└── tests/
├── output/
│ ├── Arrays.recursive.actual # valor actual
│ └── Arrays.recursive.expected # valor esperado
│
└── Arrays.recursive.phpt # prueba fallida
Podemos cambiar el nombre del directorio a través de Tester\Dumper::$dumpDir
.