Afirmaciones

Las aserciones se utilizan para afirmar que un valor real coincide con un valor esperado. Son métodos de Tester\Assert.

Elija las aserciones más precisas. Es mejor Assert::same($a, $b) que Assert::true($a === $b) porque muestra un mensaje de error significativo en caso de fallo. En el segundo caso sólo obtenemos false should be true y no dice nada sobre el contenido de las variables $a y $b.

La mayoría de las aserciones también pueden tener un $description opcional que aparece en el mensaje de error si la expectativa falla.

Los ejemplos asumen que el siguiente alias de clase está definido:

use Tester\Assert;

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

$expected debe ser el mismo que $actual. Es el mismo que el operador PHP ===.

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

Opuesto a Assert::same(), por lo que es el 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 ignoran la identidad del objeto, el orden de los pares clave ⇒ valor en matrices, y los números decimales marginalmente diferentes, que pueden cambiarse configurando $matchIdentity y $matchOrder.

Los siguientes casos son idénticos desde el punto de vista de equal(), pero no para same():

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

Sin embargo, cuidado, la matriz [1, 2] y [2, 1] no son iguales, porque sólo difiere el orden de los valores, no los pares clave ⇒ valor. El array [1, 2] también puede escribirse como [0 => 1, 1 => 2] y por tanto [1 => 2, 0 => 1] se considerará igual.

También puede utilizar las llamadas expectativas en $expected.

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 una matriz, 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 una matriz y no debe contener la clave $needle.

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

$value debe ser true, por lo que $value === true.

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

$value debe ser verdadero, por lo que satisface la condición if ($value) ....

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

$value debe ser false, por lo que $value === false.

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

$value debe ser falsa, por lo que satisface la condición if (!$value) ....

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

$value debe ser null, por lo que $value === null.

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

$value no debe ser null, entonces $value !== null.

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

$value debe ser Not a Number. Utilice únicamente Assert::nan() para las pruebas NAN. El valor NAN es muy específico y las aserciones Assert::same() o Assert::equal() pueden comportarse de forma impredecible.

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

El número de elementos en $value debe ser $count. Por tanto, igual que count($value) === $count.

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

$value debe ser de un tipo determinado. Como $type podemos utilizar string:

  • array
  • list – array indexado en orden ascendente de claves numéricas desde cero
  • bool
  • callable
  • float
  • int
  • null
  • object
  • resource
  • scalar
  • string
  • nombre de clase u objeto directamente entonces debe pasar $value instanceof $type

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

Al invocar $callable debe lanzarse una excepción de instancia $class. Si pasamos $message, el mensaje de la excepción debe coincidir. Y si pasamos $code, código de la excepción debe ser el mismo.

Por ejemplo, esta prueba falla porque el mensaje de la excepción no coincide:

Assert::exception(
	fn() => throw new App\InvalidValueException('Valor cero'),
	App\InvalidValueException::class,
	'El valor es demasiado bajo',
);

El Assert::exception() devuelve una excepción lanzada, por lo que puede probar una excepción anidada.

$e = Assert::exception(
	fn() => throw new MyException('Algo va mal', 0, new RuntimeException),
	MyException::class,
	'Algo va mal',
);

Assert::type(RuntimeException::class, $e->getPrevious());

Assert::error(string $callable, int|string|array $type, string $message=null)

Comprueba que la invocación $callable genera los errores esperados (es decir, advertencias, avisos, etc.). Como $type especificamos una de las constantes E_..., por ejemplo E_WARNING. Y si pasamos $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ás errores, debemos esperarlos todos en el orden exacto. En este caso pasamos el array en $type:

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

Si $type es el nombre de la clase, esta aserción se comporta igual que Assert::exception().

Assert::noError(callable $callable)

Comprueba que la función $callable no lanza ningún warning/notice/error o excepción de PHP. Es útil para probar un fragmento de código en el que no hay ninguna otra aserción.

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

$actual debe coincidir con $pattern. Podemos utilizar dos variantes de patrones: expresiones regulares o comodines.

Si pasamos una expresión regular como $pattern, debemos utilizar ~ or # para delimitarla. No se admiten otros delimitadores. Por ejemplo test donde $var debe contener sólo dígitos hexadecimales:

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

La otra variante es similar a la comparación de cadenas pero podemos utilizar algunos caracteres comodín en $pattern:

  • %a% uno o más de cualquier cosa excepto los caracteres de fin de línea
  • %a?% cero o más de cualquier cosa excepto los caracteres de fin de línea
  • %A% uno o más de cualquier cosa incluyendo los caracteres de fin de línea
  • %A?% cero o más caracteres, incluidos los de final de línea
  • %s% uno o más caracteres de espacio en blanco excepto los caracteres de final de línea
  • %s?% cero o más caracteres de espacio en blanco excepto los caracteres de final de línea
  • %S% uno o más de caracteres excepto el espacio en blanco
  • %S?% cero o más de caracteres excepto el espacio en blanco
  • %c% un solo carácter de cualquier tipo (excepto el final 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 HEX
  • %w% uno o más caracteres alfanuméricos
  • %% un carácter %

Ejemplos:

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

La aserción es idéntica a Assert::match() pero el patrón se carga desde $file. Es útil para probar cadenas muy largas. El archivo de prueba es legible.

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

Esta afirmación siempre falla. Sólo es útil. Podemos pasar opcionalmente valores esperados y reales.

Expectativas

Si 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 hash de la contraseña, pero sabemos que debe ser una cadena hexadecimal. Y lo único que sabemos sobre el siguiente elemento es que debe ser un objeto DateTime.

En estos casos, podemos utilizar el Tester\Expect dentro del parámetro $expected de los métodos Assert::equal() y Assert::notEqual(), que pueden utilizarse para describir fácilmente la estructura.

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, podemos hacer casi las mismas afirmaciones que con Assert. Así que tenemos métodos como Expect::same(), Expect::match(), Expect::count(), etc. Además, podemos encadenarlos como:

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

O, podemos escribir nuestros propios manejadores de aserción.

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

Investigación de aserciones fallidas

El Comprobador muestra dónde está el error cuando falla una aserción. Cuando comparamos estructuras complejas, el Probador crea volcados de los valores comparados y los guarda en el directorio output. Por ejemplo, cuando la prueba imaginaria Arrays.recursive.phpt falla, los volcados se guardan de la siguiente manera:

app/
└── tests/
	├── output/
	│ ├── Arrays.recursive.actual    # valor real.
	│ └── Arrays.recursive.expected  # valor esperado
	│
	└── Arrays.recursive.phpt        # prueba fallida

Podemos cambiar el nombre del directorio por Tester\Dumper::$dumpDir.