Las pruebas unitarias son cruciales en el desarrollo de software, ya que garantizan que los componentes de tu aplicación funcionan como se espera de ellos de forma aislada. Al escribir pruebas para unidades de código específicas, puedes identificar y corregir errores en una fase temprana del desarrollo, lo que conduce a un software más fiable y estable.

En un flujo de integración continua/entrega continua (CI/CD), puedes ejecutar estas pruebas automáticamente después de realizar cambios en el código base. Esto garantiza que el nuevo código no introduzca errores ni rompa la funcionalidad existente.

Este artículo destaca la importancia de las pruebas unitarias en las aplicaciones Laravel, detallando cómo escribir pruebas unitarias para una aplicación Laravel desplegada utilizando el servicio de Alojamiento de Aplicaciones de Kinsta.

Introducción a PHPUnit

PHPUnit es un framework de pruebas ampliamente utilizado dentro del ecosistema PHP diseñado para pruebas unitarias. Dispone de un robusto conjunto de herramientas para crear y ejecutar pruebas, lo que lo convierte en un recurso fundamental para garantizar la fiabilidad y calidad de tu código base.

Laravel soporta pruebas con PHPUnit e incluye prácticos métodos de ayuda que te permiten probar tu aplicación.

Configurar PHPUnit en un proyecto Laravel implica una configuración mínima. Laravel proporciona un entorno de pruebas preconfigurado, que incluye un archivo phpunit.xml y un directorio tests (de pruebas) dedicado para tus archivos de prueba.

Alternativamente, puedes modificar el archivo phpunit.xml para definir opciones personalizadas para una experiencia de pruebas a medida. También puedes crear un archivo de entorno .env.testing en la carpeta raíz del proyecto en lugar de utilizar el archivo .env.

Diseño predeterminado de pruebas en Laravel

Laravel proporciona un diseño de directorio predeterminado estructurado. El directorio raíz de tu proyecto Laravel contiene un directorio tests con los subdirectorios Feature y Unit . Este diseño facilita la separación de los distintos tipos de pruebas y mantiene un entorno de pruebas limpio y organizado.

El archivo phpunit.xml de un proyecto Laravel es crucial para orquestar el proceso de pruebas, garantizar la coherencia en las ejecuciones de pruebas y permitirte personalizar el comportamiento de PHPUnit según los requisitos del proyecto. Te permite definir cómo ejecutar las pruebas, incluyendo la definición de los conjuntos de pruebas, la especificación del entorno de pruebas y la configuración de las conexiones a la base de datos.

Este archivo también especifica que la sesión, la caché y el correo electrónico deben establecerse en el controlador del array, lo que garantiza que no persistan datos de sesión, caché o correo electrónico al ejecutar las pruebas.

Puedes realizar varios tipos de pruebas en tu aplicación Laravel:

  • Pruebas unitarias — se centran en componentes individuales de tu código, como clases, métodos y funciones. Estas pruebas permanecen aisladas de la aplicación Laravel y verifican que determinadas unidades de código funcionan como se espera. Ten en cuenta que las pruebas definidas en el directorio tests/Unit no inician la aplicación Laravel, lo que significa que no pueden acceder a la base de datos ni a otros servicios que ofrece el framework.
  • Pruebas de características — validan la funcionalidad más amplia de tu aplicación. Estas pruebas simulan peticiones y respuestas HTTP, permitiéndote probar rutas, controladores y la integración de varios componentes. Las pruebas de características ayudan a garantizar que las distintas partes de tu aplicación funcionan juntas como se espera.
  • Pruebas de navegador — van más allá automatizando las interacciones del navegador. Las pruebas utilizan Laravel Dusk, una herramienta de pruebas y automatización del navegador, para simular las interacciones del usuario, como rellenar formularios y hacer clic en botones. Las pruebas de navegador son cruciales para validar el comportamiento de tu aplicación y la experiencia del usuario en navegadores del mundo real.

Conceptos de desarrollo guiado por pruebas

El desarrollo dirigido por pruebas (TDD, Test-driven development) es un enfoque de desarrollo de software que hace hincapié en la realización de pruebas antes de implementar el código. Este enfoque sigue un proceso conocido como ciclo rojo-verde-refactorización.

El desarrollo guiado por pruebas mostrando rojo-verde-refactor.
El ciclo de desarrollo dirigido por pruebas mostrando rojo-verde-refactorización.

Aquí tienes una explicación de este ciclo:

  • Fase roja — Escribe una nueva prueba para definir la funcionalidad o una mejora de una existente antes de implementar el código real. La prueba debe fallar (como significa «rojo») porque no hay código correspondiente para hacerla pasar.
  • Fase verde — Escribe el código suficiente para que la prueba que falla pase, convirtiéndola de roja a verde. El código no será óptimo, pero cumple los requisitos del caso de prueba correspondiente.
  • Fase de refactorización — Refactoriza el código para mejorar su legibilidad, mantenimiento y rendimiento sin cambiar su comportamiento. En esta fase, puedes realizar cómodamente cambios en el código sin preocuparte por los problemas de regresión, ya que los casos de prueba existentes los detectan.

TDD tiene varias ventajas:

  • Detección temprana de errores — TDD ayuda a detectar errores al principio del proceso de desarrollo, contribuyendo a reducir el coste y el tiempo de solucionar problemas más adelante en el ciclo de desarrollo.
  • Diseño mejorado — TDD fomenta el código modular y poco acoplado para mejorar el diseño del software. Te anima a pensar en la interfaz y en las interacciones de los componentes antes de la implementación.
  • Confianza en la refactorización — Puedes refactorizar el código con confianza, sabiendo que las pruebas existentes identifican rápidamente cualquier regresión introducida durante la refactorización.
  • Documentación viva — Los casos de prueba sirven como documentación viva al proporcionar ejemplos de cómo debe comportarse el código. Esta documentación está siempre actualizada, ya que los fallos en las pruebas indican problemas en el código.

En el desarrollo de Laravel, aplicas los principios de TDD escribiendo pruebas para componentes como controladores, modelos y servicios antes de implementarlos.

El entorno de pruebas de Laravel, incluido PHPUnit, proporciona métodos y aserciones convenientes para facilitar el TDD, asegurando que puedas crear pruebas significativas y seguir el ciclo rojo-verde-refactor de forma eficaz.

Ejemplos básicos de pruebas unitarias

Esta sección explica cómo escribir una prueba sencilla para comprobar la funcionalidad de tu modelo.

Requisitos previos

Para seguir adelante, necesitas lo siguiente:

  • Cumplir los requisitos previos enumerados en la guía del blog de Laravel.
  • Una aplicación Laravel. Este tutorial utiliza la aplicación creada en la guía enlazada anteriormente. Puedes leerla y crear la aplicación del blog, pero si sólo necesitas el código fuente para implementar las pruebas, sigue los pasos que se indican a continuación.
  • Xdebug instalado y configurado con el modo de cobertura activado.

Configura el proyecto

  1. Ejecuta este comando en una ventana de terminal para clonar el proyecto.
    git clone https://github.com/VirtuaCreative/kinsta-laravel-blog.git
  2. Entra en la carpeta del proyecto y ejecuta el comando composer install para instalar las dependencias del proyecto.
  3. Cambia el nombre del archivo env.example por .env.
  4. Ejecuta el comando php artisan key:generate para generar una clave de aplicación.

Crear y ejecutar pruebas

Para empezar, asegúrate de que tienes el código del proyecto en tu máquina. El modelo que vas a probar es el modelo Post definido en el archivo app/Http/Models/Post.php. Este modelo engloba varios atributos rellenables, como title, description, y image.

Tu tarea consiste en elaborar pruebas unitarias sencillas para este modelo. Una verifica que los atributos están configurados correctamente, mientras que otra examina la asignación masiva intentando asignar un atributo no rellenable.

  1. Ejecuta el comando php artisan make:test PostModelFunctionalityTest --unit para crear un nuevo caso de prueba. La opción --unit especifica que se trata de una prueba unitaria y la guarda en el directorio tests/Unit .
  2. Abre el archivo tests/Unit/PostModelFunctionalityTest.php y sustituye la función test_example por este código:
    public function test_attributes_are_set_correctly()
       {
           // create a new post instance with attributes
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // check if you set the attributes correctly
           $this->assertEquals('Sample Post Title', $post->title);
           $this->assertEquals('Sample Post Description', $post->description);
           $this->assertEquals('sample_image.jpg', $post->image);
       }
    
       public function test_non_fillable_attributes_are_not_set()
       {
           // Attempt to create a post with additional attributes (non-fillable)
           $post = new Post([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
               'author' => 'John Doe',
           ]);
    
           // check that the non-fillable attribute is not set on the post instance
           $this->assertArrayNotHasKey('author', $post->getAttributes());
       }

    Este código define dos métodos de prueba.

    El primero crea una instancia Post con los atributos especificados y, utilizando el método de aserción assertEquals, comprueba que has establecido correctamente los atributos title, description y image.

    El segundo método intenta crear una instancia Post con un atributo adicional no rellenable (author) y afirma que este atributo no está establecido en la instancia del modelo utilizando el método de aserción assertArrayNotHasKey.

  3. Asegúrate de añadir la siguiente declaración use en el mismo archivo:
    use App\Models\Post;
  4. Ejecuta el comando php artisan config:clear para borrar la caché de configuración.
  5. Para ejecutar estas pruebas, ejecuta el siguiente comando:
    php artisan test tests/Unit/PostModelFunctionalityTest.php

    Todas las pruebas deberían pasar, y el terminal debería mostrar los resultados y el tiempo total de ejecución de las pruebas.

Depurar pruebas

Si las pruebas fallan, puedes depurarlas siguiendo estos pasos:

  1. Revisa el mensaje de error en el terminal. Laravel proporciona mensajes de error detallados que identifican el problema. Lee atentamente el mensaje de error para comprender por qué ha fallado la prueba.
  2. Inspecciona las pruebas y el código que estás probando para identificar discrepancias.
  3. Asegúrate de que has configurado correctamente los datos y dependencias necesarias para la prueba.
  4. Utiliza herramientas de depuración como la función dd() de Laravel para inspeccionar variables y datos en puntos concretos de tu código de prueba.
  5. Una vez que hayas identificado el problema, haz los cambios necesarios y vuelve a ejecutar las pruebas hasta que pasen.

Pruebas y bases de datos

Laravel proporciona una forma cómoda de configurar un entorno de pruebas utilizando una base de datos SQLite en memoria, que es rápida y no persiste los datos entre ejecuciones de pruebas. Para configurar el entorno de la base de datos de pruebas y escribir pruebas que interactúen con la base de datos, sigue los pasos que se indican a continuación:

  1. Abre el archivo phpunit.xml y descomenta las siguientes líneas de código:
    <env name="DB_CONNECTION" value="sqlite"/>
    <env name="DB_DATABASE" value=":memory:"/>
  2. Ejecuta el comando php artisan make:test PostCreationTest --unit para crear un nuevo caso de prueba.
  3. Abre el archivo tests/Unit/PostCreationTest.php y sustituye el método test_example por el siguiente código:
    public function testPostCreation()
       {
           // Create a new post and save it to the database
           $post = Post::create([
               'title' => 'Sample Post Title',
               'description' => 'Sample Post Description',
               'image' => 'sample_image.jpg',
           ]);
    
           // Retrieve the post from the database and assert its existence
           $createdPost = Post::find($post->id);
           $this->assertNotNull($createdPost);
           $this->assertEquals('Sample Post Title', $createdPost->title);
       }
  4. Asegúrate de añadir la siguiente sentencia use:
    use App\Models\Post;

    Actualmente, la clase PostCreationTest extiende la clase base PHPUnitFrameworkTestCase. La clase base se utiliza habitualmente para pruebas unitarias cuando se trabaja con PHPUnit directamente, fuera de Laravel, o cuando se escriben pruebas para un componente no fuertemente acoplado a Laravel. Sin embargo, si necesitas acceder a la base de datos, deberás modificar la clase PostCreationTest para que extienda la clase TestsTestCase.

    Esta última clase adapta la clase PHPUnitFrameworkTestCase a las aplicaciones Laravel. Proporciona funcionalidad adicional y configuración específica de Laravel, como la siembra de la base de datos y la configuración del entorno de pruebas.

  5. Asegúrate de sustituir la declaración use PHPUnitFrameworkTestCase; por use TestsTestCase;. Recuerda que configuraste el entorno de pruebas para utilizar una base de datos SQLite en memoria. Por tanto, debes migrar la base de datos antes de ejecutar las pruebas. Para ello, utiliza el trait IlluminateFoundationTestingRefreshDatabase. Este trait migra la base de datos si el esquema no está actualizado y reinicia la base de datos después de cada prueba para asegurarse de que los datos de la prueba anterior no interfieren con las pruebas posteriores.
  6. Añade la siguiente sentencia use al archivo tests/Unit/PostCreationTest.php para incorporar este trait a tu código:
    use Illuminate\Foundation\Testing\RefreshDatabase;
  7. A continuación, añade la siguiente línea de código justo antes del método testPostCreation:
    use RefreshDatabase;
  8. Ejecuta el comando php artisan config:clear para borrar la caché de configuración.
  9. Para realizar esta prueba, ejecuta el siguiente comando:
    php artisan test tests/Unit/PostCreationTest.php

    Las pruebas deberían pasar, y el terminal debería mostrar los resultados de la prueba y el tiempo total de la misma.

Pruebas de características

Mientras que las pruebas unitarias comprueban componentes individuales de la aplicación de forma aislada, las pruebas de características comprueban partes más amplias del código, como la forma en que interactúan varios objetos. Las pruebas de características son vitales por varias razones:

  1. Validación de extremo a extremo — Confirma que toda la función funciona a la perfección, incluidas las interacciones entre varios componentes como controladores, modelos, vistas e incluso la base de datos.
  2. Pruebas de extremo a extremo — Abarcan todo el flujo de usuario, desde la solicitud inicial hasta la respuesta final, lo que puede descubrir problemas que las pruebas unitarias podrían pasar por alto. Esta capacidad las hace valiosas para probar recorridos de usuario y escenarios complejos.
  3. Garantía de la experiencia del usuario — Imita las interacciones del usuario, ayudando a verificar una experiencia de usuario coherente y que la función funciona según lo previsto.
  4. Detección de regresiones — Detecta regresiones y cambios que rompen el código al introducir código nuevo. Si una función existente empieza a fallar en una prueba de funciones, es señal de que algo se ha roto.

Ahora, crea una prueba de funcionalidad para PostController en el archivo app/Http/Controllers/PostController.php. Céntrate en el método store, validando los datos entrantes y creando y almacenando entradas en la base de datos.

La prueba simula que un usuario crea una nueva entrada a través de una interfaz web, asegurándose de que el código almacena la entrada en la base de datos y redirige al usuario a la página Índice de Entradas tras la creación. Para ello, sigue estos pasos:

  1. Ejecuta el comando php artisan make:test PostControllerTest para crear un nuevo caso de prueba en el directorio tests/Features.
  2. Abre el archivo tests/Feature/PostControllerTest.php y sustituye el método test_example por este código:
    use RefreshDatabase; // Refresh the database after each test
    
       public function test_create_post()
       {
           // Simulate a user creating a new post through the web interface
           $response = $this->post(route('posts.store'), [
               'title' => 'New Post Title',
               'description' => 'New Post Description',
               'image' => $this->create_test_image(),
           ]);
    
           // Assert that the post is successfully stored in the database
           $this->assertCount(1, Post::all());
    
           // Assert that the user is redirected to the Posts Index page after post creation
           $response->assertRedirect(route('posts.index'));
       }
    
       // Helper function to create a test image for the post
       private function create_test_image()
       {
           // Create a mock image file using Laravel's UploadedFile class
           $file = UploadedFile::fake()->image('test_image.jpg');
    
           // Return the path to the temporary image file
           return $file;
       }

    La función test_create_post simula que un usuario crea una nueva entrada realizando una solicitud POST a la ruta posts.store con atributos específicos, incluida una imagen simulada generada mediante la clase UploadedFile de Laravel.

    A continuación, la prueba afirma que el código ha almacenado correctamente la entrada en la base de datos comprobando el recuento de Post::all(). Verifica que el código redirige al usuario a la página Índice de Entradas tras la creación de la entrada.

    Esta prueba garantiza que la funcionalidad de creación de entradas funciona y que la aplicación gestiona correctamente las interacciones con la base de datos y las redirecciones tras el envío de entradas.

  3. Añade las siguientes sentencias use al mismo archivo:
    use App\Models\Post;
    use Illuminate\Http\UploadedFile;
  4. Ejecuta el comando php artisan config:clear para borrar la caché de configuración.
  5. Para realizar esta prueba, ejecuta este comando:
    php artisan test tests/Feature/PostControllerTest.php

    La prueba debería pasar, y el terminal debería mostrar los resultados de la prueba y el tiempo total de ejecución de la misma.

Confirmar la cobertura de la prueba

La cobertura de las pruebas se refiere a la parte del código base que comprueban tus pruebas unitarias, de características o de navegador, expresada en porcentaje. Te ayuda a identificar las áreas no probadas de tu código base y las áreas insuficientemente probadas que pueden contener errores.

Herramientas como la función de cobertura de código de PHPUnit y el informe de cobertura integrado de Laravel generan informes que muestran qué partes de tu código base cubren tus pruebas. Este proceso proporciona información crítica sobre la calidad de tus pruebas y te ayuda a centrarte en las áreas que podrían requerir pruebas adicionales.

Generar un informe

  1. Elimina los archivos tests/Feature/ExampleTest.php y tests/Unit/ExampleTest.php, ya que no los has modificado y podrían causar errores.
  2. Ejecuta el comando php artisan test --coverage en una ventana de terminal. Deberías recibir una salida como la siguiente:
    Muestra el número total de pruebas superadas y el tiempo de ejecución de los resultados. También enumera cada componente de tu base de código y su porcentaje de cobertura del código.
    Ejecución del comando php artisan test –coverage.

    El informe de cobertura del código muestra los resultados de las pruebas, el número total de pruebas superadas y el tiempo de ejecución de los resultados. También enumera cada componente de tu base de código y su porcentaje de cobertura del código. Los porcentajes representan la proporción del código que cubren tus pruebas.

    Por ejemplo, Models/Post tiene una cobertura del 100%, lo que significa que todos los métodos y líneas de código del modelo están cubiertos. El informe de cobertura del código también muestra la Cobertura Total — la cobertura global del código de toda la base de código. En este caso, las pruebas cubren sólo el 65,3% del código.

  3. Para especificar un umbral mínimo de cobertura, ejecuta el comando php artisan test --coverage --min=85. Este comando establece un umbral mínimo del 85%. Deberías recibir la siguiente salida:
    Captura de pantalla que muestra el informe de cobertura con un umbral mínimo del 85%.La línea inferior indica que la prueba falla porque no alcanza el umbral mínimo.
    Prueba con un umbral mínimo del 85%.

    El conjunto de pruebas falla porque el código no cumple el umbral mínimo establecido del 85%.

    Aunque lograr una mayor cobertura del código — a menudo del 100% — es el objetivo, es más importante probar a fondo las partes críticas y complejas de tu aplicación.

Resumen

Si adoptas las mejores prácticas descritas en este artículo, como escribir pruebas significativas y exhaustivas, respetar el ciclo rojo-verde-refactorización en TDD y aprovechar las funciones de prueba que proporcionan Laravel y PHPUnit, podrás crear aplicaciones robustas y de alta calidad.

Además, tienes la opción de alojar tu aplicación Laravel con la infraestructura rápida, segura y fiable de Kinsta. Además, puedes utilizar la API de Kinsta para iniciar despliegues dentro de tus flujos de CI/CD a través de plataformas como GitHub Actions, CircleCI, etc.

Jeremy Holcombe Kinsta

Editor de Contenidos y Marketing en Kinsta, Desarrollador Web de WordPress y Redactor de Contenidos. Aparte de todo lo relacionado con WordPress, me gusta la playa, el golf y el cine. También tengo problemas con la gente alta ;).