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 cero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nombre de clase o directamente un objeto, entonces $value debe ser instanceof $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.