Leccion 69 de 75 12 min de lectura

PHPUnit Basico

PHPUnit es el framework de testing mas utilizado en PHP. Proporciona herramientas para escribir y ejecutar tests de forma estructurada y profesional.

Instalacion

PHPUnit se instala como dependencia de desarrollo con Composer:

bash
# Instalar PHPUnit como dependencia de desarrollo
composer require --dev phpunit/phpunit

# Verificar instalacion
./vendor/bin/phpunit --version

Estructura del proyecto

Por convencion, los tests se colocan en una carpeta tests/ separada del codigo fuente:

text
mi-proyecto/
├── src/
│   └── Calculadora.php
├── tests/
│   └── CalculadoraTest.php
├── composer.json
└── phpunit.xml

Configuracion basica

Crea un archivo phpunit.xml en la raiz del proyecto:

xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         colors="true"
         bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="Tests">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

Tu primer test con PHPUnit

Primero, el codigo que vamos a testear:

PHP
<?php
// src/Calculadora.php

declare(strict_types=1);

namespace App;

class Calculadora
{
    public function sumar(int $a, int $b): int
    {
        return $a + $b;
    }

    public function restar(int $a, int $b): int
    {
        return $a - $b;
    }

    public function dividir(float $a, float $b): float
    {
        if ($b === 0.0) {
            throw new \InvalidArgumentException('No se puede dividir entre cero');
        }

        return $a / $b;
    }
}

Ahora el test correspondiente:

PHP
<?php
// tests/CalculadoraTest.php

declare(strict_types=1);

namespace Tests;

use App\Calculadora;
use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase
{
    public function testSumarDosNumeros(): void
    {
        // Arrange
        $calc = new Calculadora();

        // Act
        $resultado = $calc->sumar(2, 3);

        // Assert
        $this->assertSame(5, $resultado);
    }

    public function testRestarDosNumeros(): void
    {
        $calc = new Calculadora();

        $resultado = $calc->restar(10, 4);

        $this->assertSame(6, $resultado);
    }
}
Convencion de nombres

Las clases de test terminan en Test y extienden TestCase. Los metodos de test empiezan con test o usan el atributo #[Test].

Ejecutar tests

bash
# Ejecutar todos los tests
./vendor/bin/phpunit

# Ejecutar un archivo especifico
./vendor/bin/phpunit tests/CalculadoraTest.php

# Ejecutar un test especifico
./vendor/bin/phpunit --filter testSumarDosNumeros

La salida muestra el resultado de cada test:

text
PHPUnit 11.0.0

..                                                 2 / 2 (100%)

Time: 00:00.005, Memory: 6.00 MB

OK (2 tests, 2 assertions)

Assertions comunes

PHPUnit ofrece muchos metodos de asercion para verificar resultados:

PHP
<?php

declare(strict_types=1);

// Igualdad estricta (tipo y valor)
$this->assertSame(5, $resultado);        // Pasa si 5 === $resultado
$this->assertNotSame(5, $resultado);     // Pasa si 5 !== $resultado

// Igualdad no estricta
$this->assertEquals(5, $resultado);       // Pasa si 5 == $resultado

// Booleanos
$this->assertTrue($valor);
$this->assertFalse($valor);

// Null
$this->assertNull($valor);
$this->assertNotNull($valor);

// Arrays
$this->assertCount(3, $array);           // Tiene 3 elementos
$this->assertContains('a', $array);      // Contiene 'a'
$this->assertArrayHasKey('id', $array);  // Tiene clave 'id'
$this->assertEmpty($array);              // Array vacio

// Strings
$this->assertStringContainsString('hola', $texto);
$this->assertStringStartsWith('Hola', $texto);
$this->assertMatchesRegularExpression('/\d+/', $texto);

// Tipos
$this->assertInstanceOf(Usuario::class, $objeto);

Testear excepciones

Para verificar que el codigo lanza excepciones cuando debe:

PHP
<?php

declare(strict_types=1);

namespace Tests;

use App\Calculadora;
use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase
{
    public function testDividirEntreCeroLanzaExcepcion(): void
    {
        $calc = new Calculadora();

        // Declarar que esperamos una excepcion
        $this->expectException(\InvalidArgumentException::class);
        $this->expectExceptionMessage('No se puede dividir entre cero');

        // Este codigo debe lanzar la excepcion
        $calc->dividir(10, 0);
    }
}

Data providers

Los data providers permiten ejecutar el mismo test con diferentes datos:

PHP
<?php

declare(strict_types=1);

namespace Tests;

use App\Calculadora;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class CalculadoraTest extends TestCase
{
    public static function datosParaSuma(): array
    {
        return [
            'positivos' => [2, 3, 5],
            'negativos' => [-2, -3, -5],
            'mixtos' => [-5, 10, 5],
            'con cero' => [0, 5, 5],
        ];
    }

    #[DataProvider('datosParaSuma')]
    public function testSumaConDiferentesDatos(int $a, int $b, int $esperado): void
    {
        $calc = new Calculadora();

        $resultado = $calc->sumar($a, $b);

        $this->assertSame($esperado, $resultado);
    }
}

PHPUnit ejecuta el test una vez por cada conjunto de datos, mostrando cual fallo si alguno no pasa.

setUp y tearDown

Estos metodos se ejecutan antes y despues de cada test, utiles para preparar y limpiar el entorno:

PHP
<?php

declare(strict_types=1);

namespace Tests;

use App\Calculadora;
use PHPUnit\Framework\TestCase;

class CalculadoraTest extends TestCase
{
    private Calculadora $calculadora;

    protected function setUp(): void
    {
        // Se ejecuta ANTES de cada test
        $this->calculadora = new Calculadora();
    }

    protected function tearDown(): void
    {
        // Se ejecuta DESPUES de cada test
        // Limpiar recursos, archivos temporales, etc.
    }

    public function testSumar(): void
    {
        // Ya tenemos $this->calculadora disponible
        $this->assertSame(5, $this->calculadora->sumar(2, 3));
    }

    public function testRestar(): void
    {
        $this->assertSame(6, $this->calculadora->restar(10, 4));
    }
}

Ejercicios

Ejercicio 1: Escribir un test basico

Escribe tests para esta clase:

PHP
<?php

declare(strict_types=1);

class StringHelper
{
    public function invertir(string $texto): string
    {
        return strrev($texto);
    }

    public function contarPalabras(string $texto): int
    {
        return str_word_count($texto);
    }
}
Ver solucion
PHP
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

class StringHelperTest extends TestCase
{
    private StringHelper $helper;

    protected function setUp(): void
    {
        $this->helper = new StringHelper();
    }

    public function testInvertirTexto(): void
    {
        $resultado = $this->helper->invertir('hola');

        $this->assertSame('aloh', $resultado);
    }

    public function testInvertirTextoVacio(): void
    {
        $resultado = $this->helper->invertir('');

        $this->assertSame('', $resultado);
    }

    public function testContarPalabras(): void
    {
        $resultado = $this->helper->contarPalabras('Hola mundo cruel');

        $this->assertSame(3, $resultado);
    }

    public function testContarPalabrasTextoVacio(): void
    {
        $resultado = $this->helper->contarPalabras('');

        $this->assertSame(0, $resultado);
    }
}

Ejercicio 2: Test con excepcion

Escribe un test que verifique que esta funcion lanza una excepcion con edad negativa:

PHP
<?php

declare(strict_types=1);

class Persona
{
    public function __construct(
        public readonly string $nombre,
        public readonly int $edad
    ) {
        if ($edad < 0) {
            throw new InvalidArgumentException('La edad no puede ser negativa');
        }
    }
}
Ver solucion
PHP
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;

class PersonaTest extends TestCase
{
    public function testCrearPersonaConDatosValidos(): void
    {
        $persona = new Persona('Ana', 25);

        $this->assertSame('Ana', $persona->nombre);
        $this->assertSame(25, $persona->edad);
    }

    public function testEdadNegativaLanzaExcepcion(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('La edad no puede ser negativa');

        new Persona('Juan', -5);
    }
}

Ejercicio 3: Data provider

Crea un data provider para testear esta funcion con multiples casos:

PHP
<?php

declare(strict_types=1);

function esMayorDeEdad(int $edad): bool
{
    return $edad >= 18;
}
Ver solucion
PHP
<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\DataProvider;

class EdadTest extends TestCase
{
    public static function edadesProvider(): array
    {
        return [
            'menor de edad (10)' => [10, false],
            'justo menor (17)' => [17, false],
            'justo mayor (18)' => [18, true],
            'adulto (30)' => [30, true],
            'anciano (80)' => [80, true],
        ];
    }

    #[DataProvider('edadesProvider')]
    public function testEsMayorDeEdad(int $edad, bool $esperado): void
    {
        $resultado = esMayorDeEdad($edad);

        $this->assertSame($esperado, $resultado);
    }
}

Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium