Pruenas Unitarias y de Integración con PHPUnit y Pest en Laravel

Las pruebas son una parte crucial en cualquier aplicación que vayamos a crear, sin importar la tecnología, siempre es recomendable realizar pruebas automáticas para probar el sistema cuando se implementen nuevos cambios; de esta forma nos ahorramos mucho tiempo ya que, no hay necesidad de realizar muchas de las pruebas de manera manual si no, ejecutando un simple comando.

Las pruebas consisten en probar los componentes de manera individual; en el caso de la aplicación que hemos construido, serían cada uno de los métodos de la API, al igual que cualquier otra dependencia de estos métodos; de esta manera, cuando se ejecutan estas pruebas automatizadas, si la aplicación pasa todas las pruebas, significa que no se encontraron errores, pero, si no pasa las pruebas, significa que hay que hacer cambios a nivel de la aplicación o pruebas implementadas.

¿Por qué hacer pruebas?

Las pruebas ayudan a garantizar que su aplicación funcionará como se espera y a medida que la aplicación vaya creciendo en módulos y complejidad, se puedan implementar nuevas pruebas y adaptar las actuales.

Es importante mencionar que las pruebas no son perfectas, es decir, que, aunque la aplicación pase todas las pruebas no significa que la aplicación está libre de errores, pero sí es un buen indicador inicial de la calidad del software. Además, el código comprobable es generalmente una señal de una buena arquitectura de software.

Las pruebas deben de formar parte del ciclo de desarrollo de la aplicación para garantizar su buen funcionamiento ejecutando las mismas constantemente.

¿Qué probar?

Las pruebas deberían centrarse en probar pequeñas unidades de código de forma aislada o individual.

Por ejemplo, en una aplicación Laravel o una aplicación web en general:

  • Controladores:
    • Respuestas de las vistas
    • Códigos de estados
    • Condiciones nominales (GET, POST, etc.) para una función de vista
  • Formularios
  • Funciones de ayuda individuales

En Laravel, de manera oficial tenemos dos maneras de emplear las pruebas, mediante Pest y PHPUnit.

 

Las pruebas son la base de las pruebas en Laravel,con las cuales podemos probar de forma aislada los métodos que componen a nuestra aplicación.

Pruebas con Pest/PHPUnit

PHPUnit es uno de los frameworks para realizar pruebas en PHP y que vienen ya instalado por defecto en Laravel, al ser el que ha formado parte de Laravel por más tiempo, es el que primero vamos a cubrir. Es importante aclarar que para seguir este apartado, debes de haber seleccionado PHPunit como ambiente de testing al momento de crear el proyecto en Laravel.

Al momento de crear un proyecto en Laravel, ya automáticamente también crea una carpeta de tests, con esto, puedes darte cuenta de lo importante que son las pruebas, que aunque no forman parte de desarrollo funcional de una aplicación, si forma parte del ciclo de vida de esta y crear las mismas es evidencia de buenas prácticas en la programación.

Como hemos comentado anteriormente, Laravel a partir de la versión 11, uno de los cambios más importantes es su limpieza de carpetas, quitando archivos y carpetas para fusionarlos con otros o generar bajo demanda otros como en el caso del api.php, pero, puedes ver que la carpeta de tests aún está presente apenas creamos el proyecto, dando una total evidencia de la importancia de la creación de estas pruebas para todas las aplicaciones que desarrollemos.

Por defecto, la carpeta de tests contiene dos carpetas:

  • tests/Feature
  • tests/Unit

Las pruebas unitarias son aquellas que probamos un módulo concreto de la aplicación, como dice su nombre es una unidad, algo que no podemos dividir, por ejemplo, un facade, un modelo, un helper son candidatos de pruebas unitarias, ya que tienen código aislado de la aplicación y no se comunican entre sí como en el caso de los controladores o componentes, estas pruebas se almacenan en tests/Unit.

 

Mientras que la carpeta de tests/Feature es la empleada para realizar pruebas de integración, como lo son los controladores o aquellos componentes que no son individuales como en el caso anterior si no, en donde ocurren muchas cosas como conexiones la base de datos, emplear helpers, facades o similares, retornas jsons, vistas, etc. Estas pruebas se conocen como pruebas de funciones en donde probamos bloques de códigos más grandes y que usualmente resuelven una respuesta HTTP completa.

Por defecto, ya Laravel viene con algunas pruebas y archivos listos para usar, una de las pruebas de ejemplo es el de ExampleTest.php y que trae el hola mundo para nuestra aplicación.

Independientemente si estás empleando Pest o PHPUnit, la lógica es la misma, lo que cambia es la sintaxis y para ejecutar nuestras pruebas tenemos el comando de:

$ vendor/bin/phpunit

Para PHPUnit, o:

$ vendor/bin/pest

Para Pest, o más fácil:

$ php artisan test

Para cualquiera de los anteriores.

Adicionalmente, puedes crear un archivo .env.testing en la raíz de su proyecto para manejar las configuraciones en ambiente prueba. Este archivo se utilizará en lugar del archivo .env cuando se ejecuten pruebas de Pest y PHPUnit o se ejecuten comandos de Artisan con la opción --env=testing.

 

 

Carpeta de tests

 

Para crear una prueba unitaria, tenemos el siguiente comando:

$ php artisan make:test <ClassTest>

En donde la prueba se colocará en la carpeta de tests/Feature:

O si deseas crear una prueba dentro de la carpeta tests/Unit, puedes usar la opción --unit al ejecutar el comando make:test:

$ php artisan make:test <ClassTest> --unit

Más información en:

https://laravel.com/docs/master/testing

Entendiendo las pruebas

Para que se entienda de una manera menos abstracta el uso de las pruebas, vamos a crear un pequeño ejercicio de operaciones matemáticas antes de empezar a crear pruebas para probar módulos de nuestra aplicación, como serían en caso de los controladores, es decir, el esquema de request/response.

Para estas primeras pruebas, creemos un archivo de operaciones matemáticas como el siguiente:

app\Utils\MathOperations.php

class MathOperations
{
 public function add($a, $b) {
    return $a + $b;
 }

 public function subtract($a, $b) {
    return $a - $b;
 }

 public function multiply($a, $b) {
    return $a * $b;
 }

 public function divide($a, $b) {
    return $a / $b;

 }
}

Ahora vamos a generar nuestro primer archivo para la primera prueba unitaria mediante:

$ php artisan make:test MathOperationsTest --unit

Esto generará un archivo en:

tests/Unit/MathOperationsTest.php

En el cual, creamos unas funciones que permitan probar los métodos anteriores para realizar operaciones matemáticas:

tests/Unit/MathOperationsTest.php

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

// use App\Utils\MathOperations;

class MathOperations
{

    public function add($a, $b)
    {
        return $a + $b;
    }


    public function subtract($a, $b)
    {
        return $a - $b;
    }


    public function multiply($a, $b)
    {
        return $a * $b;
    }

    public function divide($a, $b)
    {
        return $a / $b;
    }
}

class MathOperationsTest extends TestCase
{

    // public function test_example(): void
    // {
    //     $this->assertTrue(true);
    // }

 public function testAdd()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->add(2, 3);
        $this->assertEquals(5, $result);
    }
    public function testSubtract()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->subtract(5, 3);
        $this->assertEquals(2, $result);
    }
    public function testSubtraction()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->multiply(5, 3);
        $this->assertEquals(15, $result);
    }
    public function testDivide()
    {
        $mathOperations = new MathOperations();
        $result = $mathOperations->divide(8, 2);
        $this->assertEquals(4, $result);
    }
}

 

Para facilitar el ejercicio, copiamos el contenido de MathOperations dentro del archivo unitario.

En este ejemplo, tenemos cuatro métodos de prueba, uno por cada método definido en la clase auxiliar MathOperations que permite probar las operaciones de suma, resta, multiplicación y división respectivamente y con esto podemos apreciar el corazón de las pruebas que es mediante métodos assert o métodos de tipo aserción:

  • assertStatus: Verifica el código de estado en la respuesta.
  • assertOk: Verifica si la respuesta obtenida es de tipo 200. 
  • assertJson: Verifica si la respuesta es de tipo JSON.
  • assertRedirect: Verifica si la respuesta es una redirección.
  • assertSee: Verifica en base a un string suministrador, si forma parte de la respuesta.
  • assertDontSee: Verifica si el string suministrado no forma parte de la respuesta.
  • assertViewIs: Verifica si la vista fue retornada por la ruta.
  • assertValid: Verifica si no hay errores de validación en el formulario enviado.

Que no son más que condicionales con los cuales verificamos que se obtengan los resultados esperados, en este ejemplo, se emplea el método assertEquals pero existen varios con un funcionamiento similar e iremos viendo algunos de ellos a lo largo del capítulo.

Puedes ver la inmensa lista completa en:

https://laravel.com/docs/master/http-tests#response-assertions

Aunque no te preocupes por tener que aprenderlos todos, usualmente usamos unos pocos de ellos.

Finalmente, para ejecutar las pruebas unitarias, usamos el comando de:

$ vendor/bin/phpunit

Y deberíamos de ver una salida como la siguiente:

Time: 00:00.850, Memory: 42.50 MB

OK (29 tests, 65 assertions)

Si provocamos algún error en la la clase auxiliar, como sumar dos veces el mismo parámetro, ignorando el otro:

public function add($a, $b)
{
    return $a + $a;
}

Y ejecutamos:

$ vendor/bin/phpunit

Veremos una salida como la siguiente:

/MathOperationsTest.php:47

FAILURES!
Tests: 29, Assertions: 65, Failures: 1.

Que indica claramente de que ocurrió un error.

Las pruebas unitarias no son infalibles, ya que, todo depende de las pruebas que ejecutemos, manteniendo el mismo error que provocamos antes, si la prueba fuera como la siguiente:

public function testAdd()
{
    $mathOperations = new MathOperations();
    $result = $mathOperations->add(2, 2);
    $this->assertEquals(4, $result);
}

Las pruebas pasarían:

Time: 00:00.733, Memory: 42.50 MB
OK (29 tests, 65 assertions)

Pero, claramente tenemos un problema en la definición de la clase auxiliar, por lo tanto, las pruebas no son infalibles, son solamente un medio para verificar que no encontramos errores en la aplicación pero no significa de que la aplicación está libre de errores, con esto, podemos tener un entendimiento bsasçio y necesario de cómo funcionan las pruebas unitarias, con este ejemplo, podemos ahora a pasar a probar realmente módulos que conforman la aplicación.

Peticiones HTTP

Nuestra aplicación está formada por controladores o similares que son consumidos mediante peticiones HTTP de distintos tipos y es justamente lo que usualmente debemos de probar; las pruebas unitarias están formadas de dos bloques principales, los métodos de aspiración y los métodos http, que en PHPUnit, tenemos un método por cada método HTTP, es decir métodos get, post, put, patch, o delete; para poder enviar peticiones HTTP; debemos de heredar de la clase:

use Tests\TestCase;

- Andrés Cruz

In english

Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos Curso y libro Laravel 11 con Tailwind Vue 3, introducción a Jetstream Livewire e Inerta desde cero - 2024.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.