Laravel
Tutorial Laravel: Pruebas unitarias con PHPUnit
Descubre cómo usar PHPUnit en Laravel para realizar pruebas unitarias eficientes que mejoran la calidad y consistencia de tus aplicaciones.
Aprende Laravel GRATIS y certifícateIntroducción a PHPUnit en Laravel
Laravel integra de forma nativa PHPUnit, un framework de testing para PHP que permite realizar pruebas unitarias automatizadas. PHPUnit facilita la verificación de que el código funciona como se espera, lo cual es esencial en el desarrollo de aplicaciones robustas.
Al utilizar PHPUnit en Laravel, se aprovechan las facilidades que ofrece el framework para escribir pruebas de manera eficiente. Laravel proporciona una estructura predefinida y utilidades específicas para el testing, como el acceso a la base de datos en el entorno de pruebas y la simulación de solicitudes HTTP mediante el método actingAs
.
El archivo de configuración phpunit.xml, incluido en los proyectos de Laravel, define el entorno de pruebas y las directivas necesarias para ejecutar las pruebas correctamente. Además, Laravel extiende la clase base de PHPUnit mediante TestCase, permitiendo acceder a características propias del framework dentro de las pruebas.
Para ejecutar las pruebas, se utiliza el comando Artisan específico de Laravel:
php artisan test
Este comando inicia el runner de pruebas y muestra los resultados de manera detallada. Ofrece una salida más legible y opciones avanzadas, como la ejecución de pruebas en paralelo o el filtrado por nombre de clase o método.
La integración de PHPUnit en Laravel promueve la adopción de prácticas de desarrollo orientadas a pruebas, conocidas como Desarrollo Dirigido por Pruebas (TDD). Implementar pruebas unitarias desde el inicio del proyecto mejora la calidad del código y reduce el número de errores en producción.
Creación de pruebas unitarias
Para comenzar a escribir pruebas unitarias en Laravel, es necesario crear clases de prueba que extiendan de Tests\TestCase
o directamente de PHPUnit\Framework\TestCase
. Estas clases se ubican en el directorio tests/Unit
, el cual está destinado a albergar pruebas unitarias que verifican la funcionalidad de componentes individuales sin interactuar con otros sistemas o dependencias externas.
Para generar una clase de prueba unitaria, se utiliza el comando Artisan:
php artisan make:test NombreDeLaPrueba --unit
Este comando crea una nueva clase de prueba en tests/Unit/NombreDeLaPrueba.php
. El uso de la opción --unit
especifica que se trata de una prueba unitaria, y por defecto la clase generada extenderá de PHPUnit\Framework\TestCase
.
Dentro de la clase de prueba, se definen métodos que representan cada caso de prueba. Es una buena práctica que estos métodos comiencen con la palabra test
o utilicen la anotación @test
en el comentario del método. Por ejemplo:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
class CalculadoraTest extends TestCase
{
public function testSumaDeDosNumeros()
{
$resultado = 5 + 3;
$this->assertEquals(8, $resultado);
}
}
En este ejemplo, el método testSumaDeDosNumeros
verifica que la operación de suma funciona correctamente. La aserción assertEquals
comprueba que el valor obtenido coincide con el esperado.
Es posible también utilizar la anotación @test
para definir métodos de prueba sin necesidad de que el nombre del método comience con test
:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
/**
* @test
*/
public function sumaDeDosNumeros()
{
$resultado = 5 + 3;
$this->assertEquals(8, $resultado);
}
Para configurar el entorno antes de cada prueba, se puede implementar el método setUp
proporcionado por PHPUnit. Este método se ejecuta antes de cada test y es útil para inicializar objetos o establecer condiciones previas comunes:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Models\User;
class UsuarioTest extends TestCase
{
protected $usuario;
protected function setUp(): void
{
parent::setUp();
$this->usuario = new User();
}
public function testNombreUsuarioNoNulo()
{
$this->usuario->name = 'Juan';
$this->assertNotNull($this->usuario->name);
}
}
En este caso, se crea una instancia de User antes de cada prueba, asegurando que cada test trabaje con un objeto fresco y sin modificaciones previas.
Las pruebas unitarias deben ser independientes entre sí, evitando que el resultado de una influya en las demás. Esto mejora la fiabilidad de las pruebas y facilita la detección de errores aislados.
Para ejecutar las pruebas unitarias, se utiliza el siguiente comando:
php artisan test --testsuite=Unit
Este comando ejecuta todas las pruebas del grupo Unit, es decir, las que se encuentran en el directorio tests/Unit
. Si se desea ejecutar una prueba específica, se puede indicar la ruta al archivo o incluso especificar un método concreto:
php artisan test tests/Unit/UsuarioTest.php
php artisan test --filter testNombreUsuarioNoNulo
Laravel proporciona diversas herramientas que facilitan la creación de pruebas unitarias. Por ejemplo, es posible utilizar factories para generar modelos con datos predefinidos, lo cual es útil para preparar el entorno de prueba con información realista.
Además, es recomendable aprovechar las funcionalidades de autocarga de PHPUnit para organizar y estructurar las pruebas de manera coherente. Mantener un orden lógico y una nomenclatura consistente en las clases y métodos de prueba mejora la mantenibilidad del código y la eficiencia en el desarrollo.
La creación de pruebas unitarias es un pilar fundamental en el desarrollo de software de calidad. Ayuda a detectar errores de forma temprana, facilita el refactoring y asegura que nuevas funcionalidades no introduzcan regresiones en el comportamiento existente.
Aserciones comunes
En PHPUnit, las aserciones son fundamentales para verificar que el código funciona según lo esperado. Una aserción es una afirmación que comprueba si una condición es verdadera. Si la condición no se cumple, la prueba falla y se notifica el error.
Una de las aserciones más utilizadas es assertEquals
, que verifica si dos valores son iguales:
public function testSuma()
{
$resultado = 2 + 2;
$this->assertEquals(4, $resultado);
}
Para comprobar si un valor es verdadero, se emplea assertTrue
:
public function testVerdadero()
{
$activo = true;
$this->assertTrue($activo);
}
Si se necesita verificar que un valor es falso, se utiliza assertFalse
:
public function testFalso()
{
$activo = false;
$this->assertFalse($activo);
}
La aserción assertNull
comprueba que una variable es nula:
public function testEsNulo()
{
$valor = null;
$this->assertNull($valor);
}
Para asegurarse de que una variable no es nula, se utiliza assertNotNull
:
public function testNoEsNulo()
{
$valor = 'Laravel';
$this->assertNotNull($valor);
}
Cuando se desea verificar que una variable es del tipo esperado, se usa assertInstanceOf
:
public function testInstanciaDeClase()
{
$usuario = new User();
$this->assertInstanceOf(User::class, $usuario);
}
La aserción assertCount
permite comprobar el número de elementos en un array o colección:
public function testNumeroDeElementos()
{
$nombres = ['Ana', 'Luis', 'Carlos'];
$this->assertCount(3, $nombres);
}
Para verificar que un array contiene un valor específico, se utiliza assertContains
:
public function testContieneValor()
{
$frutas = ['manzana', 'pera', 'naranja'];
$this->assertContains('pera', $frutas);
}
Si se quiere comprobar que un array asociativo tiene una clave determinada, se emplea assertArrayHasKey
:
public function testTieneClave()
{
$datos = ['nombre' => 'María', 'edad' => 30];
$this->assertArrayHasKey('edad', $datos);
}
La aserción assertSame
verifica que dos variables son idénticas, es decir, que tienen el mismo valor y tipo:
public function testSonIdenticos()
{
$a = 5;
$b = 5;
$this->assertSame($a, $b);
}
Por el contrario, assertNotSame
comprueba que dos variables no son idénticas:
public function testNoSonIdenticos()
{
$a = '5';
$b = 5;
$this->assertNotSame($a, $b);
}
En pruebas que involucran cadenas de texto, assertStringContainsString
verifica que una cadena contiene otra:
public function testContieneCadena()
{
$mensaje = 'Bienvenido a Laravel';
$this->assertStringContainsString('Laravel', $mensaje);
}
Para comprobar que una cadena no contiene cierto texto, se utiliza assertStringNotContainsString
:
public function testNoContieneCadena()
{
$mensaje = 'Hola Mundo';
$this->assertStringNotContainsString('Laravel', $mensaje);
}
Cuando se trabaja con excepciones, se puede utilizar expectException
para asegurarse de que se lanza la excepción esperada:
public function testLanzaExcepcion()
{
$this->expectException(\InvalidArgumentException::class);
throw new \InvalidArgumentException('Argumento inválido.');
}
Además, assertFileExists
verifica que un archivo existe en el sistema:
public function testArchivoExiste()
{
$ruta = storage_path('app/documento.pdf');
$this->assertFileExists($ruta);
}
Para comprobar que un archivo no existe, se emplea assertFileDoesNotExist
:
public function testArchivoNoExiste()
{
$ruta = storage_path('app/inexistente.pdf');
$this->assertFileDoesNotExist($ruta);
}
La aserción assertJson
es útil para validar que una cadena es un JSON válido:
public function testEsJsonValido()
{
$json = '{"nombre": "Luis", "edad": 25}';
$this->assertJson($json);
}
Si se necesita verificar que una clase utiliza un trait específico, se puede utilizar assertContains
junto con la función class_uses
:
public function testUsaTrait()
{
$this->assertContains(SoftDeletes::class, class_uses(User::class));
}
En el contexto de modelos de Laravel, assertDatabaseHas
permite comprobar que existe un registro en la base de datos:
public function testRegistroEnBaseDeDatos()
{
$this->assertDatabaseHas('users', [
'email' => 'usuario@example.com',
]);
}
Para verificar que un registro no existe, se utiliza assertDatabaseMissing
:
public function testRegistroNoEnBaseDeDatos()
{
$this->assertDatabaseMissing('users', [
'email' => 'noexiste@example.com',
]);
}
Es importante utilizar las aserciones adecuadas para cada situación, ya que proporcionan mensajes de error más claros y ayudan a identificar rápidamente la causa de un fallo. Las aserciones son la base de las pruebas unitarias y contribuyen significativamente a la calidad y mantenibilidad del código.
Al escribir pruebas, es recomendable ser preciso y descriptivo tanto en los nombres de los métodos como en las aserciones utilizadas. Esto facilita la comprensión de lo que se está verificando y hace las pruebas más útiles como documentación del comportamiento esperado.
Mocks y stubs
En el contexto de las pruebas unitarias, es común utilizar mocks y stubs para simular el comportamiento de dependencias externas o componentes que no se desea probar directamente. En Laravel, gracias a PHPUnit y herramientas adicionales, es posible crear mocks y stubs que facilitan el aislamiento del código bajo prueba.
Un stub es un objeto simulado que proporciona respuestas predefinidas a llamadas realizadas durante la prueba, sin implementar una lógica compleja. Por otro lado, un mock es un objeto que no solo simula el comportamiento, sino que también verifica que se hayan realizado interacciones específicas, como llamadas a métodos con parámetros concretos.
Para utilizar mocks y stubs en Laravel, se puede emplear la biblioteca Mockery, que se integra de forma nativa con PHPUnit y Laravel. Mockery permite crear objetos simulados de manera sencilla y precisa, lo cual es especialmente útil al probar componentes como servicios, repositorios o clases que interactúan con APIs externas.
Por ejemplo, supongamos que tenemos un servicio que depende de un cliente HTTP para hacer peticiones a una API externa. No es práctico realizar llamadas reales durante las pruebas, por lo que se puede crear un mock del cliente HTTP:
<?php
namespace Tests\Unit;
use Tests\TestCase;
use Mockery;
use App\Services\MiServicio;
use GuzzleHttp\Client;
class MiServicioTest extends TestCase
{
public function testObtenerDatosExitosamente()
{
$respuestaMock = ['dato' => 'valor'];
$clienteMock = Mockery::mock(Client::class);
$clienteMock->shouldReceive('get')
->with('https://api.ejemplo.com/datos')
->andReturn($respuestaMock);
$servicio = new MiServicio($clienteMock);
$resultado = $servicio->obtenerDatos();
$this->assertEquals($respuestaMock, $resultado);
}
}
En este ejemplo, se crea un mock de Client
de Guzzle HTTP, simulando la respuesta del método get
. Se establece que cuando se llame a get
con la URL especificada, el mock devolverá una respuesta predefinida. De esta manera, el test verifica el funcionamiento de MiServicio
sin realizar peticiones reales.
Es importante limpiar los mocks después de cada prueba para evitar efectos secundarios. Para ello, se puede utilizar el método tearDown
:
protected function tearDown(): void
{
Mockery::close();
parent::tearDown();
}
Además de Mockery, PHPUnit ofrece mecanismos propios para crear mocks y stubs sin necesidad de librerías externas. Por ejemplo:
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Services\MiServicio;
use App\Repositories\UserRepository;
class MiServicioTest extends TestCase
{
public function testContarUsuarios()
{
$repositorioMock = $this->createMock(UserRepository::class);
$repositorioMock->method('obtenerTodos')
->willReturn([
['id' => 1, 'nombre' => 'Ana'],
['id' => 2, 'nombre' => 'Luis'],
]);
$servicio = new MiServicio($repositorioMock);
$resultado = $servicio->contarUsuarios();
$this->assertEquals(2, $resultado);
}
}
Aquí se utiliza el método createMock
de PHPUnit para crear un stub de UserRepository
. Se define el comportamiento del método obtenerTodos
para que devuelva una lista de usuarios simulada. De esta forma, se puede probar el método contarUsuarios
del servicio sin acceder a la base de datos.
Cuando se requiere verificar que un método se ha llamado con ciertos parámetros, se pueden utilizar las aserciones de PHPUnit:
public function testEliminarArchivo()
{
$archivo = 'ruta/al/archivo.txt';
$filesystemMock = $this->getMockBuilder(Filesystem::class)
->onlyMethods(['delete'])
->getMock();
$filesystemMock->expects($this->once())
->method('delete')
->with($archivo)
->willReturn(true);
$servicio = new MiServicio($filesystemMock);
$resultado = $servicio->eliminarArchivo($archivo);
$this->assertTrue($resultado);
}
En este caso, se verifica que el método delete
de Filesystem
se ha llamado exactamente una vez con el parámetro especificado. Esto es útil para asegurarse de que el código interactúa correctamente con sus dependencias.
Los spies son otra técnica relacionada que permite registrar las interacciones con las dependencias y verificar las llamadas después de ejecutarse el código bajo prueba. Mockery facilita la creación de spies de manera sencilla:
public function testEnviarNotificacion()
{
$notificadorMock = Mockery::spy(Notificador::class);
$servicio = new MiServicio($notificadorMock);
$servicio->procesar();
$notificadorMock->shouldHaveReceived('enviar')
->with('Mensaje de prueba')
->once();
}
Con el spy, se puede comprobar que el método enviar
ha sido llamado con los parámetros correctos.
En Laravel, es común utilizar la función mock
o spy
para sustituir facades o clases del framework durante las pruebas. Por ejemplo, para mockear la facade Mail
:
public function testEnvioDeEmail()
{
Mail::fake();
// Ejecutar acción que envía el email
$this->post('/registro', ['email' => 'usuario@example.com']);
// Verificar que se envió el email
Mail::assertSent(RegistroEmail::class, function ($mail) {
return $mail->hasTo('usuario@example.com');
});
}
En este ejemplo, se utiliza Mail::fake()
para evitar el envío real de correos electrónicos durante la prueba. Luego, se verifica que el correo RegistroEmail
se ha enviado al destinatario correcto.
Del mismo modo, se pueden mockear otras facades como Queue
, Event
o Notification
para controlar y verificar su comportamiento sin ejecutar su lógica real.
El uso adecuado de mocks y stubs permite aislar el código bajo prueba, centrándose en su lógica interna sin depender de externalidades. Esto hace que las pruebas sean más confiables, rápidas y fáciles de mantener.
Es importante recordar que el abuso de mocks puede conducir a pruebas frágiles o que no reflejen el comportamiento real de la aplicación. Por ello, se recomienda mockear solo las dependencias externas o costosas, y mantener el balance entre pruebas unitarias y de integración.
Ejecución de pruebas y reporting
Para ejecutar las pruebas en Laravel, se utiliza el comando php artisan test
, que es una envoltura sobre PHPUnit adaptada al framework. Este comando busca y ejecuta todas las pruebas ubicadas en los directorios tests/Unit
y tests/Feature
.
Al ejecutar php artisan test
, se obtiene una salida en la consola que muestra el progreso y los resultados de cada prueba. Las pruebas exitosas se representan con un punto .
, mientras que las fallidas muestran una F
y proporcionan detalles del error.
Para una salida más legible, se puede utilizar la opción --testdox
, que muestra los nombres de las pruebas de forma más descriptiva:
php artisan test --testdox
Este comando genera una salida tipo:
Tests\Unit\UsuarioTest
✔ Puede crear un usuario
✔ El nombre del usuario no puede estar vacío
Tests: 2 passed
Si se desea ejecutar una prueba específica, se puede utilizar la opción --filter
, indicando el nombre del método o clase de prueba:
php artisan test --filter=testNombreUsuarioNoNulo
Es posible también ejecutar un archivo de prueba en particular, especificando su ruta:
php artisan test tests/Unit/UsuarioTest.php
Para aumentar la eficiencia, Laravel permite ejecutar las pruebas en paralelo mediante la opción --parallel
. Esto distribuye las pruebas en varios procesos simultáneos, reduciendo el tiempo total de ejecución:
php artisan test --parallel
El comando anterior detecta automáticamente el número de núcleos del sistema y asigna los procesos en consecuencia. Si se desea especificar el número de procesos, se puede usar la opción --processes
:
php artisan test --parallel --processes=4
Para obtener información más detallada durante la ejecución, se puede agregar la opción -v
o --verbose
al comando, lo que muestra mensajes adicionales:
php artisan test --verbose
La configuración de PHPUnit se define en el archivo phpunit.xml
ubicado en la raíz del proyecto. En este archivo, se pueden ajustar diversas opciones, como incluir bootstrap, establecer variables de entorno y configurar el reporting.
Para generar informes de cobertura de código, es necesario tener instalada una extensión como Xdebug. Una vez configurado, se puede ejecutar el comando:
php artisan test --coverage
Este comando muestra en la consola un resumen de la cobertura alcanzada por las pruebas. Para generar un informe más detallado en formato HTML, se utiliza:
php artisan test --coverage-html=coverage-report
Este comando crea un directorio coverage-report
con archivos HTML que permiten visualizar gráficamente qué partes del código están cubiertas por las pruebas. Es una herramienta invaluable para identificar áreas que requieren más atención.
En el archivo phpunit.xml
, se puede configurar el reporting añadiendo las siguientes líneas dentro de la sección <logging>
:
<logging>
<log type="coverage-html" target="coverage-report" />
<log type="junit" target="junit-report.xml" />
</logging>
Con esta configuración, al ejecutar las pruebas, se generarán informes en HTML y en formato JUnit, que es útil para integraciones con herramientas de Integración Continua.
Para personalizar aún más la ejecución de pruebas, PHPUnit admite diversas opciones que se pueden combinar con php artisan test
. Por ejemplo, para ejecutar las pruebas en orden aleatorio, se utiliza:
php artisan test --order-by=random
Esto ayuda a detectar dependencias entre pruebas que podrían causar resultados inconsistentes.
Si se desea detener la ejecución al primer error, se puede agregar la opción --stop-on-failure
:
php artisan test --stop-on-failure
Esta opción es útil para centrar la atención en el primer fallo y corregirlo antes de continuar.
Para ignorar temporalmente ciertas pruebas, se pueden utilizar anotaciones como @skip
o @todo
. Por ejemplo:
/**
* @skip
*/
public function testFuncionalidadPendiente()
{
// ...
}
Este método será omitido durante la ejecución de las pruebas.
Laravel también permite monitorear los cambios en el código y re-ejecutar las pruebas automáticamente utilizando la opción --watch
:
php artisan test --watch
Con esta opción, el comando observa los archivos del proyecto y ejecuta las pruebas cada vez que detecta una modificación, facilitando el desarrollo iterativo.
Para mejorar la legibilidad de los nombres de las pruebas en la salida, se recomienda utilizar el formato camelCase en los nombres de los métodos o emplear anotaciones como @test
con descripciones claras.
Es posible filtrar las pruebas por grupo utilizando la anotación @group
en los métodos de prueba:
/**
* @group rapido
*/
public function testOperacionRapida()
{
// ...
}
Luego, se ejecutan únicamente las pruebas de ese grupo:
php artisan test --group=rapido
La comprensión e interpretación de los informes es esencial para mantener un código de calidad. Al analizar la cobertura de código, se deben priorizar las áreas críticas y los componentes más utilizados.
Integrar las pruebas en un flujo de CI/CD es una práctica recomendada. Al configurar un pipeline que ejecute las pruebas automáticamente, se garantiza que las contribuciones al código cumplen con los estándares establecidos.
Las herramientas de Integración Continua como Jenkins, GitLab CI o GitHub Actions pueden utilizar los informes generados para mostrar resultados en paneles de control y notificar a los desarrolladores en caso de fallos.
Por ejemplo, al utilizar GitHub Actions, se puede crear un workflow que ejecute las pruebas en cada push:
name: Run Tests
on: [push, pull_request]
jobs:
phpunit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- name: Install Dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Run Tests
run: php artisan test
Esta configuración asegura que las pruebas se ejecuten automáticamente en cada cambio, manteniendo la integridad del proyecto.
En entornos de desarrollo local, es recomendable ejecutar las pruebas de forma frecuente. Las pruebas actúan como una red de seguridad, detectando errores antes de que lleguen a producción.
Mejores prácticas
Al escribir pruebas unitarias con PHPUnit en Laravel, es esencial seguir una serie de mejores prácticas que garanticen la calidad y mantenibilidad del código de pruebas. A continuación, se detallan recomendaciones clave para optimizar el proceso de testing y obtener resultados más robustos y confiables.
Nombrado claro y descriptivo: Los nombres de las clases y métodos de prueba deben reflejar claramente lo que se está verificando. Utilizar una convención de nomenclatura consistente facilita la comprensión y el mantenimiento. Por ejemplo, un método como testUsuarioPuedeRegistrarseConDatosValidos
es más descriptivo que testRegistro
.
Pruebas independientes: Cada prueba debe ser autónoma y no depender del estado generado por otras pruebas. Esto implica evitar el uso de variables estáticas o globales que puedan alterar el comportamiento. Las pruebas independientes aseguran resultados consistentes y permiten ejecutar tests de forma aislada sin efectos colaterales.
Configuración y limpieza adecuadas: Utilizar los métodos setUp
y tearDown
para preparar el entorno antes de cada prueba y limpiar después de su ejecución. Esto incluye la creación de datos necesarios y la restauración del estado inicial. Por ejemplo:
protected function setUp(): void
{
parent::setUp();
// Preparar datos de prueba
$this->usuario = User::factory()->create();
}
protected function tearDown(): void
{
// Limpiar datos creados
User::truncate();
parent::tearDown();
}
Evitar duplicación de código: Reutilizar código común mediante métodos auxiliares o traits. Si varias pruebas comparten lógica similar, extraer esa lógica en métodos privados ayuda a reducir la duplicación y mejora la legibilidad. Por ejemplo:
private function crearUsuarioAutenticado()
{
$usuario = User::factory()->create();
$this->actingAs($usuario);
return $usuario;
}
Usar aserciones específicas: Seleccionar la aserción más adecuada para cada caso en lugar de recurrir siempre a assertTrue
o assertEquals
. Las aserciones específicas proporcionan mensajes de error más claros y ayudan a identificar problemas con mayor precisión. Por ejemplo, utilizar assertCount
al verificar el número de elementos en lugar de comparar manualmente el conteo.
Mocking vs. testing real: Decidir cuándo es apropiado utilizar mocks o simular dependencias. En las pruebas unitarias, es común mockear dependencias externas para aislar la unidad bajo prueba. Sin embargo, en pruebas de integración, puede ser preferible utilizar componentes reales. Es importante encontrar el equilibrio y no abusar de los mocks, ya que pueden ocultar problemas en las interacciones reales entre componentes.
Cobertura de casos de error y bordes: Además de probar los casos típicos de uso, es fundamental validar cómo se comporta el código ante entradas inválidas o situaciones excepcionales. Esto incluye pruebas con datos nulos, vacíos o que provoquen excepciones. Garantizar que el sistema maneje adecuadamente estos casos mejora la robustez y la resiliencia.
Mantener el rendimiento de las pruebas: Las pruebas deben ser rápidas para fomentar su ejecución frecuente. Evitar operaciones costosas innecesarias, como acceso a la base de datos cuando no es requerido, o procesamiento intensivo. Utilizar técnicas como el uso de factories en lugar de fixtures pesadas y limitar el uso de recursos externos.
Escribir pruebas primero (TDD): Adoptar la práctica del Desarrollo Orientado por Pruebas (TDD) puede mejorar significativamente la calidad del código. Escribir las pruebas antes de la implementación ayuda a definir claramente los requisitos y a diseñar interfaces más limpias y orientadas a las necesidades reales.
Documentar intenciones, no implementaciones: Las pruebas deben centrarse en el comportamiento esperado, no en los detalles internos de la implementación. Esto significa que las pruebas deben ser resistentes a cambios en cómo se hace algo, siempre que el resultado final sea el mismo. Evitar acoplar las pruebas a aspectos internos que puedan variar, como algoritmos o estructuras privadas.
Utilizar data providers para múltiples casos: Cuando se necesita probar el mismo comportamiento con diferentes conjuntos de datos, los data providers de PHPUnit facilitan la creación de pruebas parametrizadas. Esto reduce la repetición y mejora la cobertura de casos.
/**
* @dataProvider proveedorDeNumeros
*/
public function testEsPar($numero, $esperado)
{
$resultado = $this->esPar($numero);
$this->assertEquals($esperado, $resultado);
}
public function proveedorDeNumeros()
{
return [
[2, true],
[3, false],
[0, true],
[-4, true],
];
}
Integración con herramientas de CI/CD: Integrar la ejecución de pruebas en los procesos de Integración Continua y Despliegue Continuo garantiza que las pruebas se ejecuten constantemente y que el código que entra en producción cumple con los estándares de calidad. Configurar pipelines que ejecuten las pruebas en cada commit o pull request ayuda a detectar problemas temprano.
Escribir pruebas legibles y mantenibles: Las pruebas deben ser tan claras y legibles como el código de producción. Utilizar nombres descriptivos, estructurar el código de manera coherente y comentar cuando sea necesario. Esto es especialmente importante en entornos colaborativos donde múltiples desarrolladores pueden trabajar y mantener el conjunto de pruebas.
Actualizar las pruebas al cambiar el código: Cuando se realizan cambios en el código de producción, es crucial revisar y actualizar las pruebas correspondientes. Las pruebas obsoletas o incorrectas pueden dar una falsa sensación de seguridad y dificultar la detección de errores reales.
Evitar pruebas frágiles: Las pruebas que dependen de elementos externos susceptibles a cambios, como contenido dinámico o factores temporales, pueden resultar frágiles y fallar sin que haya errores en el código. Por ejemplo, evitar depender de la fecha y hora actual sin controlarla explícitamente en la prueba. Utilizar herramientas como Carbon::setTestNow() para controlar el tiempo en las pruebas.
Aprovechar las funcionalidades de Laravel: Laravel ofrece múltiples herramientas que facilitan el testing, como factories, seeders, simulación de eventos y métodos de ayuda como actingAs
. Aprovechar estas funcionalidades puede simplificar las pruebas y alinearlas con las convenciones del framework.
Seguir el principio FIRST: Las pruebas deben ser Rápidas (Fast), Independientes (Independent), Repetibles (Repeatable), Auto-validables (Self-validating) y En tiempo (Timely). Este principio ayuda a mantener un conjunto de pruebas eficaz y útil a lo largo del tiempo.
Gestionar dependencias adecuadamente: Al trabajar con la base de datos u otros recursos compartidos, utilizar transacciones o estados conocidos para asegurar que las pruebas no interfieran entre sí. Laravel permite utilizar la trait RefreshDatabase para resetear la base de datos antes de cada prueba:
use RefreshDatabase;
public function testCrearUsuario()
{
$response = $this->post('/usuarios', ['nombre' => 'Carlos']);
$response->assertStatus(201);
$this->assertDatabaseHas('users', ['nombre' => 'Carlos']);
}
Verificar resultados esperados y no solo estados intermedios: Las pruebas deben enfocarse en validar los resultados finales y el comportamiento observable del sistema, no únicamente en las llamadas internas o métodos privados. Esto asegura que las pruebas sean orientadas al usuario y que capturen el valor real de las funcionalidades.
Mantener actualizada la suite de pruebas: Revisar periódicamente el conjunto de pruebas para eliminar aquellas que ya no son relevantes y agregar nuevas que cubran funcionalidades añadidas o modificadas. Una suite de pruebas actual es esencial para reflejar el estado real del proyecto.
Fomentar la cultura de testing en el equipo: Promover la importancia de las pruebas dentro del equipo de desarrollo, compartiendo conocimientos y facilitando recursos para que todos los miembros puedan contribuir al conjunto de pruebas. Un enfoque colaborativo en el testing mejora la calidad general del software.
Siguiendo estas mejores prácticas, se puede construir un conjunto de pruebas sólido y eficaz que respalde el desarrollo de aplicaciones Laravel confiables y de alta calidad. Las pruebas unitarias no solo detectan errores, sino que también sirven como documentación viva del comportamiento esperado, facilitando el mantenimiento y evolución del software a largo plazo.
Todas las lecciones de Laravel
Accede a todas las lecciones de Laravel y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Php Laravel
Introducción Y Entorno
Instalación Y Configuración Laravel
Introducción Y Entorno
Controladores Mvc
Controladores Http
Vistas Y Blade Templates
Controladores Http
Formularios Y Validación
Controladores Http
Controladores Rest
Controladores Http
Middleware Y Control De Solicitudes
Persistencia
Seguridad Autenticación Y Autorización
Persistencia
Bases De Datos Y Eloquent Orm
Persistencia
Relaciones Entre Modelos
Persistencia
Consultas Avanzadas
Persistencia
Colecciones Y Métodos Avanzados
Persistencia
Migraciones Y Seeders
Persistencia
Sistema De Autenticación Nativo Laravel
Middlewares Y Seguridad
Autorización (Policies Y Gates)
Middlewares Y Seguridad
Csrf Y Protección De Formularios En Blade
Middlewares Y Seguridad
Validaciones De Datos En Controladores Y Formularios
Middlewares Y Seguridad
Cifrado De Contraseñas
Middlewares Y Seguridad
Autenticación Jwt En Api Rest
Middlewares Y Seguridad
Pruebas Unitarias Con Phpunit
Testing
Pruebas De Integración En Laravel
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender qué es y para qué sirve PHPUnit en Laravel. - Configurar el archivo phpunit.xml para pruebas.
- Crear y ejecutar pruebas unitarias utilizando Artisan.
- Implementar buenas prácticas de TDD con ejemplos.
- Integrar mocks, stubs y detallar su uso en pruebas independientes.
- Utilizar diversas aserciones para verificar condiciones en el código.
- Configurar reporting y entender los informes de cobertura de código.