Pruebas unitarias en PHP con PHPUnit

Desarrollo Web

Las pruebas unitarias ayudan a poder hacer test de nuestros módulos del sistema, ayudando a hacer más rápido el desarrollo sin tener que probar directamente en el navegador. PHPUnit es el framework de Testing más usado en PHP.

¿Qué es PHPUnit?

PHPUnit es un framework de Testing usado en aplicaciones PHP con el que podemos hacer pruebas de nuestro código, desde pruebas a clases y módulos hasta peticiones HTTP validando estatus y contenido. Nos ayuda a dejar de lado el navegador al momento de hacer código y evitar hacer los típicos echo $var para probar funcionalidades, también apoya a no recargar la página cada que hacemos algún cambio. Suele usarse el patrón TDD para el diseño de pruebas, aunque no es indispensable.

Las pruebas también sirven para en un momento avanzado del proyecto poder refactorizar código y verificar que no se haya roto nada, al correr las pruebas unitarias y verificar que todo funciona bien, podemos asegurar que no hemos quebrado la funcionalidad y que no se ha afectado otros módulos.

Finalmente, las pruebas unitarias no reemplazan el trabajo de un tester, quien usa técnicas avanzadas de pruebas al software, solo es una guía del programador para saber que está escribiendo el código correctamente.

Puede ser usado en cualquier proyecto PHP y en este apartado aprenderemos a implementarlo usando composer para instalarlo.

Instalación y configuración de PHPUnit

Instalación

En la terminal, vamos al directorio del proyecto y lo instalamos con composer. Aunque puedes instalarlo de otras formas como lo dice su página oficial.

composer require --dev phpunit/phpunit

Con esto ya podríamos ejecutar phpunit

> vendor/bin/phpunit

Configuración

Aunque no es obligatorio, recomiendo ampliamente usar un archivo de configuración en la raíz de su proyecto llamado phpunit.xml, donde especificaremos a phpunit la carpeta donde se encontrarán nuestros tests y el archivo autoload de composer. La información más básica quedaría de la siguiente manera:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit
    colors="true"
    bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite>
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

Ahora, falta crear una carpeta test y dentro pondremos nuestras pruebas. Ahora solo pondremos el siguiente código

use PHPUnit\Framework\TestCase;

class MyFirstTest extends TestCase
{
    public function testHelloWorld()
    {
        $this->assertEquals("hello world", "hello world");
    }
}

Corremos phpunit

> vendor/bin/phpunit

Y veremos un mensaje parecido al siguiente

PHPUnit 6.5.8 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 535 ms, Memory: 4.00MB

OK (1 test, 1 assertion)

Con lo cual hemos terminado de configurar e instalar PHPUnit

Aserciones

Las aserciones o Asserts son la parte fundamental de cualquier framework de testing, que, en términos generales, son comparaciones parecidas a un IF que ayudan a verificar que un módulo, clase, función, etc, esté enviando la respuesta esperada.

Un ejemplo básico es verificar que una función que hemos escrito nos envié un resultado esperado para diferentes casos. La siguiente función calcula el factorial de un número

function factorial($number) 
{
    if ($number < 2) { 
        return 1; 
    } else { 
        return ($number * factorial($number-1)); 
    } 
}

En nuestra prueba agregamos una nueva función

public function testFactorial()
{
    $this->assertEquals(factorial(5), 120);
}

Si ejecutamos phpunit veremos que se no obtenemos errores. Es importante destacar que todas las funciones que vayan a pasar por el proceso de test deben iniciar con la palabra test, de este modo phpunit sabrá que esa función debe ser probada.

Aquí una lista de algunos asserts más comunes

assertArrayHasKey

Comprueba que un objeto exista como llave dentro de un array

public function testArray()
{
    $this->assertArrayHasKey('key', ['key' => 'value']);
}

assertContains

Verifica que un elemento exista dentro de un array

public function testArray()
{
    $this->assertContains('car', ['apple', 'car', 'blue']);
}

assertDirectoryExists

Revisa que exista un directorio

public function testDirectory()
{
    $this->assertDirectoryExists('path/to/directory');
}

assertEmpty

Si un elemento es vacío la prueba será válida. Vacío es que sea null, [], '' o 0. La función inversa es assertNotEmpty.

public function testEmpty()
{
    $this->assertEmpty('path/to/directory');
}

assertEquals

Verifica que dos valores sean iguales. El inverso de esta función es assertNotEquals.

public function testEquals()
{
    $this->assertEquals('a', 'a');
}

assertFalse

Señala que una condición o valor sea false. El inverso es assertTrue.

public function testBoolean()
{
    $this->assertFalse('1' === 1);
}

Hemos señalado algunas de las aserciones más comunes, pero tienes todo el repertorio dentro de su documentación, donde podremos encontrar funciones para testear cadenas, json, xml, objetos, excepciones, etc.

Dependencias

Las dependencias son funciones de prueba que son necesarias para otras funciones de prueba. Esto quiere decir que en ocasiones hemos escrito una prueba pero necesitamos que se ejecute antes que la nueva prueba que escribiremos, para eso PHPUnit ha creado las dependencias. Para decirle a una prueba que ocupará alguna dependencia, dentro de los comentarios usamos la anotación @depends testFunction, donde testFunction es la función de dependencia.

Supongamos que queremos probar las funciones array_push y array_pop.

assertEquals('foo', $stack[count($stack)-1]);

        return $stack;
    }

    /**
     * @depends testPush
     */
    public function testPop($stack)
    {
        $this->assertSame('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}

Como vemos, la función testPop recibe como argumento el retorno de la función testPush, por lo que, la dependencia debe siempre regresar un valor el cual será el parámetro de la función que llama dicha dependencia. Claro que en este caso es sencillo el ejemplo, pero podríamos importar una clase y hacer pruebas con ella, donde el retorno será el objeto instanciado de la clase.

Línea de comandos

Las pruebas pueden tener filtros para no tener que llamar a que se ejecuten todas las pruebas usando la bandera --filter, donde acepta ya sea el nombre de una clase, de una función o ambas. Si encuentra más de una función con el mismo nombre, ejecutará todas.

Ejecutar una clase o función especifica

> vendor/bin/phpunit MyFirstClassTest
> vendor/bin/phpunit testFunction

Ejecutar una función de una clase especifica

> vendor/bin/phpunit TestCaseClass::testMethod

Cuando ejecutamos phpunit vemos que imprime un carácter . por cada función de prueba que es ejecutada. El punto indica que la prueba ha sido ejecutada con éxito, por lo que todas las aserciones han sido pasadas en su totalidad. Los caracteres que pueden ser impresos pueden ser los siguiente:

  • . indica que las prueba ha pasado con éxito
  • F indica que la prueba falla, es decir, alguna aserción no pasó la prueba
  • E ocurre un error al ejecutarse la prueba, es decir, el código realizó una excepción ya sea por parte de la prueba o bien del código a probar (a menos que la excepción sea controlada por una aserción)
  • R ocurre cuando la prueba ha sido marcada como riesgosa (Pruebas riesgosas)
  • S la prueba ha sido omitida (Omisión)
  • I la prueba no contiene ninguna aserción (Pruebas incompletas)

Podemos ver que otras banderas y funcionalidades tiene phpunit en la línea de comandos usando vendor/bin/phpunit --help

 

Con esto hemos terminado, en futuros Post veremos cómo crear pruebas pero usando el framework Laravel y otros tópicos más avanzados sobre pruebas unitarias.