PHPUnit es el framework de testing más usado en PHP. Permite escribir tests unitarios que verifican el comportamiento de clases y funciones de forma automática y repetible. Instalar PHPUnit, estructura los tests como clases que extienden TestCase, y ofrece aserciones, fixtures con setUp/tearDown, data providers y mocks para aislar dependencias externas.
Instalación y estructura básica
<?php
// Instalar como dependencia de desarrollo:
// composer require --dev phpunit/phpunit
// Estructura de directorios recomendada:
// src/
// Calculadora.php
// tests/
// CalculadoraTest.php
// phpunit.xml
// Clase a testear: src/Calculadora.php
namespace App;
class Calculadora
{
public function sumar(float $a, float $b): float
{
return $a + $b;
}
public function dividir(float $a, float $b): float
{
if ($b == 0.0) {
throw new InvalidArgumentException('No se puede dividir por cero.');
}
return $a / $b;
}
}
?>
TestCase y aserciones básicas
<?php
// tests/CalculadoraTest.php
namespace Tests;
use AppCalculadora;
use PHPUnitFrameworkTestCase;
class CalculadoraTest extends TestCase
{
private Calculadora $calc;
protected function setUp(): void
{
$this->calc = new Calculadora();
}
public function testSumarDosEnteros(): void
{
$resultado = $this->calc->sumar(2, 3);
$this->assertEquals(5, $resultado);
}
public function testSumarNegativo(): void
{
$this->assertEquals(-1, $this->calc->sumar(2, -3));
}
public function testDividirNormal(): void
{
$this->assertSame(2.5, $this->calc->dividir(5, 2));
}
public function testDividirPorCeroLanzaExcepcion(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('No se puede dividir por cero.');
$this->calc->dividir(10, 0);
}
}
?>
Aserciones más usadas
<?php
use PHPUnitFrameworkTestCase;
class EjemploAserciones extends TestCase
{
public function testAserciones(): void
{
// Igualdad (con conversión de tipo)
$this->assertEquals(5, '5'); // pasa
// Identidad estricta (tipo y valor)
$this->assertSame(5, 5); // pasa
// $this->assertSame(5, '5'); // falla
// Booleanos
$this->assertTrue(1 === 1);
$this->assertFalse(1 === 2);
// Nulo
$this->assertNull(null);
$this->assertNotNull('valor');
// Arrays
$this->assertCount(3, [1, 2, 3]);
$this->assertContains('php', ['python', 'php', 'js']);
$this->assertArrayHasKey('nombre', ['nombre' => 'Ana']);
// Strings
$this->assertStringContainsString('php', 'manual de php');
$this->assertMatchesRegularExpression('/^d{4}$/', '2024');
// Tipos
$this->assertIsArray([]);
$this->assertIsString('texto');
$this->assertInstanceOf(DateTime::class, new DateTime());
}
}
?>
setUp(), tearDown() y data providers
<?php
namespace Tests;
use PHPUnitFrameworkAttributesDataProvider;
use PHPUnitFrameworkTestCase;
class OperacionesTest extends TestCase
{
// Se ejecuta antes de CADA test
protected function setUp(): void
{
// inicializar fixtures, conexión a BD de test, etc.
}
// Se ejecuta después de CADA test
protected function tearDown(): void
{
// limpiar recursos, rollback de transacción, etc.
}
// Data provider: ejecuta el test con distintos conjuntos de datos
public static function casosSuma(): array
{
return [
'suma positivos' => [2, 3, 5],
'suma con cero' => [0, 5, 5],
'suma negativos' => [-2, -3, -5],
'suma mixta' => [-1, 4, 3],
];
}
#[DataProvider('casosSuma')]
public function testSumar(float $a, float $b, float $esperado): void
{
$calc = new AppCalculadora();
$this->assertSame($esperado, $calc->sumar($a, $b));
}
}
?>
Mocks con createMock()
<?php
namespace Tests;
use AppServicioRegistro;
use AppRepositoriosUsuarioRepositorioInterface;
use AppServiciosMailerInterface;
use AppServiciosLoggerInterface;
use PHPUnitFrameworkTestCase;
class ServicioRegistroTest extends TestCase
{
public function testRegistrarUsuarioNuevo(): void
{
// Crear mocks de las dependencias
$repo = $this->createMock(UsuarioRepositorioInterface::class);
$mailer = $this->createMock(MailerInterface::class);
$logger = $this->createMock(LoggerInterface::class);
// Configurar comportamiento del mock
$repo->method('buscarPorEmail')->willReturn(null); // email no existe
$repo->method('crear')->willReturn(42); // devuelve ID 42
$mailer->expects($this->once())->method('enviar'); // se llama exactamente 1 vez
$logger->expects($this->once())->method('info');
$servicio = new ServicioRegistro($repo, $mailer, $logger);
$idCreado = $servicio->registrar('Ana', '[email protected]', 'pass123');
$this->assertSame(42, $idCreado);
}
public function testRegistrarEmailDuplicadoLanzaExcepcion(): void
{
$repo = $this->createMock(UsuarioRepositorioInterface::class);
$repo->method('buscarPorEmail')->willReturn(['id' => 1]); // email ya existe
$servicio = new ServicioRegistro(
$repo,
$this->createMock(MailerInterface::class),
$this->createMock(LoggerInterface::class),
);
$this->expectException(DomainException::class);
$servicio->registrar('Ana', '[email protected]', 'pass123');
}
}
?>
phpunit.xml: configuración básica
<?php // phpunit.xml // <?xml version="1.0"?> // <phpunit bootstrap="vendor/autoload.php" // colors="true" // stopOnFailure="false"> // // <testsuites> // <testsuite name="Aplicación"> // <directory>tests/</directory> // </testsuite> // </testsuites> // // <source> // <include> // <directory>src/</directory> // </include> // </source> // </phpunit> // Ejecutar los tests: // vendor/bin/phpunit // vendor/bin/phpunit tests/CalculadoraTest.php // vendor/bin/phpunit --filter testSumar // vendor/bin/phpunit --coverage-html coverage/ ?>
La documentación oficial de PHPUnit incluye la lista completa de aserciones, el uso de test doubles (stubs, spies, fakes), integración con Xdebug para cobertura de código y la configuración de entornos de CI para ejecutar los tests automáticamente en cada push.
