Pruebas de redacción

Escribir pruebas para Nette Tester es único en que cada prueba es un script PHP que se puede ejecutar de forma independiente.. Esto tiene un gran potencial. Mientras escribes la prueba, puedes simplemente ejecutarla para ver si funciona correctamente. Si no, usted puede fácilmente pasar a través en el IDE y buscar un error.

Incluso puedes abrir la prueba en un navegador. Pero sobre todo – al ejecutarlo, realizarás la prueba. Usted sabrá inmediatamente si pasó o falló.

En el capítulo introductorio, mostramos una prueba realmente trivial del uso de un array PHP. Ahora crearemos nuestra propia clase, que probaremos, aunque también será simple.

Empecemos con una distribución típica de directorios para una librería o proyecto. Es importante separar las pruebas del resto del código, por ejemplo debido al despliegue, ya que no queremos subir las pruebas al servidor. La estructura puede ser la siguiente:

├── src/           # code that we will test
│   ├── Rectangle.php
│   └── ...
├── tests/         # tests
│   ├── bootstrap.php
│   ├── RectangleTest.php
│   └── ...
├── vendor/
└── composer.json

Y ahora crearemos archivos individuales. Empezaremos con la clase probada, que colocaremos en el archivo src/Rectangle.php

<?php
class Rectangle
{
	private float $width;
	private float $height;

	public function __construct(float $width, float $height)
	{
		if ($width < 0 || $height < 0) {
			throw new InvalidArgumentException('La dimensión no debe ser negativa.');
		}
		$this->width = $width;
		$this->height = $height;
	}

	public function getArea(): float
	{
		return $this->width * $this->height;
	}

	public function isSquare(): bool
	{
		return $this->width === $this->height;
	}
}

Y crearemos una prueba para ella. El nombre del archivo de prueba debe coincidir con la máscara *Test.php o *.phpt, elegiremos la variante RectangleTest.php:

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

// general oblong
$rect = new Rectangle(10, 20);
Assert::same(200.0, $rect->getArea()); # comprobaremos los resultados esperados
Assert::false($rect->isSquare());

Como puede ver, los métodos de aserción como Assert::same() se utilizan para afirmar que un valor real coincide con un valor esperado.

El último paso es crear el archivo bootstrap.php. Contiene un código común para todas las pruebas. Por ejemplo autoloading de clases, configuración del entorno, creación de directorios temporales, helpers y similares. Cada prueba carga el bootstrap y sólo presta atención a las pruebas. El bootstrap puede parecerse a:

<?php
require __DIR__ . '/vendor/autoload.php'; # cargar Composer autoloader

Tester\Environment::setup(); # inicialización de Nette Tester

// y otras configuraciones (sólo un ejemplo, en nuestro caso no son necesarias)
date_default_timezone_set('Europe/Prague');
define('TmpDir', '/tmp/app-tests');

Este bootstrap asume que el autocargador de Composer será capaz de cargar la clase Rectangle.php también. Esto se puede conseguir, por ejemplo, estableciendo la sección autoload en composer.json, etc.

Ahora podemos ejecutar la prueba desde la línea de comandos como cualquier otro script PHP independiente. La primera ejecución revelará cualquier error de sintaxis, y si no cometiste un error tipográfico, lo verás:

$ php RectangleTest.php

OK

Si cambiamos en el test la sentencia a false Assert::same(123, $rect->getArea());, ocurrirá lo siguiente:

$ php RectangleTest.php

Failed: 200.0 should be 123

in RectangleTest.php(5) Assert::same(123, $rect->getArea());

FAILURE

Al escribir pruebas, es bueno capturar todas las situaciones extremas. Por ejemplo, si la entrada es cero, un número negativo, en otros casos una cadena vacía, null, etc. De hecho, te obliga a pensar y decidir cómo debe comportarse el código en esas situaciones. A continuación, las pruebas corrigen el comportamiento.

En nuestro caso, un valor negativo debería lanzar una excepción, que verificamos con Assert::exception():

// la anchura no debe ser un número negativo
Assert::exception(
	fn() => new Rectangle(-1, 20),
	InvalidArgumentException::class,
	'La dimensión no debe ser negativa',
);

Y añadimos una prueba similar para la altura. Por último, comprobamos que isSquare() devuelve true si ambas dimensiones son iguales. Intenta escribir estas pruebas como ejercicio.

Pruebas bien organizadas

El tamaño del archivo de pruebas puede aumentar y saturarse rápidamente. Por lo tanto, es práctico agrupar las áreas probadas individuales en funciones separadas.

Primero mostraremos una variante más simple pero elegante, utilizando la función global test(), que por favor añada al archivo bootstrap.php:

function test(string $description, Closure $fn): void
{
	echo $description, "\n";
	$fn();
}

Usando esta función, podemos dividir agradablemente el archivo de prueba en unidades con nombre. Cuando se ejecute, las etiquetas se mostrarán una tras otra.

<?php
use Tester\Assert;

require __DIR__ . '/bootstrap.php';

test('oblongo general', function () {
	$rect = new Rectangle(10, 20);
	Assert::same(200.0, $rect->getArea());
	Assert::false($rect->isSquare());
});

test('cuadrado general', function () {
	$rect = new Rectangle(5, 5);
	Assert::same(25.0, $rect->getArea());
	Assert::true($rect->isSquare());
});

test('las dimensiones no deben ser negativas', function () {
	Assert::exception(function () {
		$rect = new Rectangle(-1, 20);
	}, InvalidArgumentException::class);

	Assert::exception(function () {
		$rect = new Rectangle(10, -1);
	}, InvalidArgumentException::class);
});

La segunda variante es objeto. Crearemos el llamado TestCase, que es una clase donde las unidades individuales están representadas por métodos cuyos nombres empiezan por test-.

class RectangleTest extends Tester\TestCase
{
	public function testGeneralOblong()
	{
		$rect = new Rectangle(10, 20);
		Assert::same(200.0, $rect->getArea());
		Assert::false($rect->isSquare());
	}

	public function testGeneralSquare()
	{
		$rect = new Rectangle(5, 5);
		Assert::same(25.0, $rect->getArea());
		Assert::true($rect->isSquare());
	}

	/** @throws InvalidArgumentException */
	public function testWidthMustNotBeNegative()
	{
		$rect = new Rectangle(-1, 20);
	}

	/** @throws InvalidArgumentException */
	public function testHeightMustNotBeNegative()
	{
		$rect = new Rectangle(10, -1);
	}
}

// Run test methods
(new RectangleTest)->run();

Esta vez utilizamos una anotación @throw para comprobar si hay excepciones. Ver el capítulo TestCase para más información.

Funciones de ayuda

Nette Tester incluye varias clases y funciones que pueden facilitarle las pruebas, por ejemplo, ayudantes para probar el contenido de un documento HTML, para probar las funciones de trabajo con archivos, etc.

Puede encontrar una descripción de los mismos en la página Ayudantes.

Anotación y omisión de pruebas

La ejecución de pruebas puede verse afectada por anotaciones en el comentario phpDoc al principio del archivo. Por ejemplo, podría verse así:

/**
 * @phpExtension pdo, pdo_pgsql
 * @phpVersion >= 7.2
 */

Las anotaciones dicen que la prueba sólo debe ejecutarse con PHP versión 7.2 o superior y si las extensiones PHP pdo y pdo_pgsql están presentes. Estas anotaciones son controladas por el ejecutor de pruebas de la línea de comandos, el cual, si no se cumplen las condiciones, se salta la prueba y la marca con la letra s – skipped. Sin embargo, no tienen ningún efecto cuando la prueba se ejecuta manualmente.

Para obtener una descripción de las anotaciones, consulte Anotaciones de prueba.

La prueba también puede omitirse en función de una condición propia con Environment::skip(). Por ejemplo, omitiremos esta prueba en Windows:

if (defined('PHP_WINDOWS_VERSION_BUILD')) {
	Tester\Environment::skip('Requires UNIX.');
}

Estructura de directorios

En el caso de bibliotecas o proyectos un poco más grandes, recomendamos dividir el directorio de prueba en subdirectorios según el espacio de nombres de la clase probada:

└── tests/
	├── NamespaceOne/
	│   ├── MyClass.getUsers.phpt
	│   ├── MyClass.setUsers.phpt
	│   └── ...
	│
	├── NamespaceTwo/
	│   ├── MyClass.creating.phpt
	│   ├── MyClass.dropping.phpt
	│   └── ...
	│
	├── bootstrap.php
	└── ...

Podrá ejecutar pruebas desde un único espacio de nombres, es decir, subdirectorio:

tester tests/NamespaceOne

Casos prácticos

Una prueba que no llame a ningún método de aserción es sospechosa y se evaluará como errónea:

Error: This test forgets to execute an assertion.

Si la prueba sin llamar a aserciones debe considerarse realmente válida, llame por ejemplo a Assert::true(true).

También puede ser traicionero utilizar exit() y die() para finalizar la prueba con un mensaje de error. Por ejemplo, exit('Error in connection') finaliza la prueba con un código de salida 0, que indica éxito. Utilice Assert::fail('Error in connection').