Volver al inicio

Testo framework: pruebas PHP y benchmarks

Testo — framework independiente para pruebas unitarias y benchmarks en PHP 8.2+. Soporta plugins, pruebas inline en src, Assert tipado. Instalación vía Composer, ejemplos de código para desarrolladores middle/senior.

Testo: nuevo framework de pruebas para PHP 8.2+
Advertisement 728x90

Testo: Un framework independiente de pruebas y benchmarking para PHP

El nuevo framework Testo ya está disponible para pruebas beta. Es una implementación autónoma, sin dependencias —no requiere PHPUnit—, compatible con PHP 8.2+, con soporte para complementos, pruebas integradas (inline tests) y comparativas de rendimiento (benchmarks). Diseñado para desarrolladores PHP de nivel medio y avanzado, Testo prioriza la flexibilidad, el tipado estricto y la reducción al mínimo del código repetitivo.

Principales diferencias frente a PHPUnit y alternativas

Testo nació para resolver limitaciones fundamentales de PHPUnit: su inflexibilidad, sus frecuentes cambios rupturistas y su estancamiento más allá del soporte básico de versiones de PHP. Su autor contribuyó activamente al repositorio de problemas y a las pull requests de PHPUnit, pero sus propuestas de mejora no fueron integradas. Por su parte, PEST y Codeception heredan esas mismas restricciones y añaden sintaxis poco intuitivas.

Testo es completamente independiente:

Google AdInline article slot
  • Evita nikic/php-parser, eliminando conflictos relacionados con el análisis sintáctico.
  • Requiere PHP 8.2 o superior.
  • Agentes de IA pueden generar pruebas automáticamente usando llms.txt —una especificación estructurada basada en la documentación oficial.

Su sistema modular de complementos permite personalizarlo por proyecto. Cada funcionalidad se entrega como un complemento opcional, y cada suite de pruebas elige los que necesita. Por ejemplo, el complemento #[Retry] aporta resiliencia ante pruebas intermitentes (flaky tests), implementado con solo dos archivos ligeros.

Amplía su comportamiento mediante pipelines, middleware y eventos. Escribe pruebas directamente en tu código de producción (src/) con el atributo #[TestInline], y usa atributos para integrar lógica de prueba de forma limpia y explícita.

Instalación y configuración

Instálalo mediante Composer:

Google AdInline article slot
composer require --dev testo/testo

Crea un archivo testo.php en la raíz de tu proyecto:

<?php

declare(strict_types=1);

use Testo/Application/Config/ApplicationConfig;
use Testo/Application/Config/SuiteConfig;

return new ApplicationConfig(
    suites: [
        new SuiteConfig(
            name: 'Fuentes',
            location: ['src'],
        ),
        new SuiteConfig(
            name: 'Pruebas',
            location: ['tests'],
        ),
    ],
);

Este archivo devuelve una instancia de ApplicationConfig. Si no existe, Testo aplica configuraciones predeterminadas (por ejemplo, escanea la carpeta tests/). Se definen dos suites por defecto: Fuentes, para pruebas integradas y benchmarks incrustados en el código productivo, y Pruebas, para pruebas unitarias tradicionales.

Ejecuta las pruebas con ./vendor/bin/testo —o usa el plugin oficial para PHPStorm para una integración perfecta con tu entorno de desarrollo.

Google AdInline article slot

Ejemplos de pruebas unitarias

No se requiere herencia. Anota los métodos con #[Test]:

final class OrderTest
{
    #[Test]
    public function calculaTotal(): void
    {
        $order = new Order();
        $order->addItem('Libro', price: 15.0, quantity: 2);
        $order->addItem('Bolígrafo', price: 3.0, quantity: 5);

        Assert::same($order->total(), 45.0);
    }

    #[Test]
    #[DataSet([100.0, 10, 90.0], '10% de descuento')]
    #[DataSet([100.0, 0, 100.0], 'sin descuento')]
    #[DataSet([0.0, 50, 0.0], 'precio cero')]
    public function aplicaDescuento(float $price, int $percent, float $expected): void
    {
        $result = Order::applyDiscount($price, $percent);

        Assert::same($result, $expected);
    }

    #[Test]
    #[ExpectException(InsufficientFundsException::class)]
    public function noPermiteSobregiro(): never
    {
        new Account(balance: 100)->withdraw(200);
    }
}

Aplica #[Test] a una clase para tratar todos sus métodos void o never como pruebas. Las funciones independientes (fuera de clases) se ejecutan sin necesidad de atributos de ciclo de vida.

Las aserciones siguen un orden intuitivo: valor real primero, valor esperado segundo. Son encadenables y seguras desde el punto de vista del tipado:

Assert::string($email)->contains('@');

Assert::int($age)->greaterThan(0)->lessThan(150);

Assert::array($items)
    ->hasKeys('id', 'name')
    ->isList()
    ->notEmpty();

Assert::json($response->body())
    ->isObject()
    ->hasKeys('data', 'meta');

Pruebas integradas y comparativas de rendimiento

Usa #[TestInline] dentro de src/ para verificar métodos privados o internos:

// src/Money.php
final class Money
{
    #[TestInline(['price' => 100.0, 'discount' => 0.1, 'tax' => 0.2], 108.0)]
    #[TestInline(['price' => 50.0, 'discount' => 0.0, 'tax' => 0.1], 55.0)]
    private static function calcularPrecioFinal(
        float $price,
        float $discount,
        float $tax,
    ): float {
        return $price * (1 - $discount) * (1 + $tax);
    }
}

Mide el rendimiento con #[Bench]:

#[Bench(
    callables: [
        'multiply' => 'viaMultiply',
        'shift'    => 'viaShift',
    ],
    arguments: [1, 5_000],
    calls: 2_000_000,
)]
function viaDivision(int $a, int $b): int
{
    $d = $b - $a + 1;
    return (int) (($d - 1) * $d / 2) + $a * $d;
}

function viaMultiply(int $a, int $b): int
{
    $d = $b - $a + 1;
    return (int) (($d - 1) * $d * 0.5) + $a * $d;
}

function viaShift(int $a, int $b): int
{
    $d = $b - $a + 1;
    return ((($d - 1) * $d) >> 1) + $a * $d;
}

Salida de ejemplo:

+---+----------+-------+---------+------------------+--------+
| # | Nombre   | Iter. | Llam.   | Tiempo promedio  | RStDev |
+---+----------+-------+---------+------------------+--------+
| 2 | actual   | 10    | 2000000 | 75.890µs         | ±0.79% |
| 3 | multiply | 10    | 2000000 | 78.821µs (+3.9%) | ±0.47% |
| 1 | shift    | 10    | 2000000 | 70.559µs (-7.0%) | ±0.70% |
+---+----------+-------+---------+------------------+--------+

Por qué es relevante

  • Un framework verdaderamente independiente: cero dependencias de PHPUnit y extensible mediante complementos modulares y componibles.
  • Pruebas integradas (#[TestInline]) y comparativas de rendimiento (#[Bench]) ubicadas directamente junto al código productivo en src/.
  • Cadenas de aserciones seguras por tipado y fluidas —sin código repetitivo, sin herencia y sin variables globales mágicas.
  • Configuración mediante archivos PHP simples, con soporte completo para proyectos con múltiples suites.
  • Plugin oficial para PHPStorm y herramientas CLI listas para usar en el día a día del desarrollo.

— Editorial Team

Advertisement 728x90

Leer después