Zpět na domů

Testo framework: testy a benchmarky PHP

Testo — nezávislý framework pro unit-testování a benchmarky v PHP 8.2+. Podporuje pluginy, vestavěné testy v src, typizované Assert. Instalace přes Composer, příklady kódu pro middle/senior-vývojáře.

Testo: nový framework testů pro PHP 8.2+
Advertisement 728x90

Testo: Nezávislý PHP framework pro jednotkové testy a benchmarky

Nový framework Testo zahajuje beta-testování. Je to samostatná implementace bez závislostí na PHPUnitu, s podporou PHP 8.2+, pluginy, vestavěné testy i benchmarky. Vyvinut pro středně pokročilé a seniorní vývojáře s důrazem na flexibilitu, přísnou typizaci a minimální množství boilerplate kódu.

Hlavní rozdíly oproti PHPUnitu a jiným alternativám

Testo vznikl kvůli omezením PHPUnitu: nedostatečná flexibilita, časté breaking changes a minimální vývoj mimo podporu nových verzí PHP. Autor se účastnil diskusí a poskytoval pull requesty, ale změny nebyly začleněny. Frameworky PEST a Codeception dědí stejné problémy jako PHPUnit a navíc mají nepohodlnou syntaxi.

Testo je zcela nezávislý:

Google AdInline article slot
  • Nepoužívá knihovnu nikic/php-parser, čímž se vyhýbá konfliktům.
  • Plná podpora PHP 8.2+.
  • AI agenti generují testy automaticky z dokumentace (llms.txt).

Systém pluginů umožňuje přizpůsobit nástroj konkrétnímu projektu. Všechny funkce jsou volitelné pluginy — každý testovací soubor (suite) má svůj vlastní výběr. Například plugin #[Retry] umožňuje opakované spouštění testů — stačí dvě malé třídy.

Rozšiřitelnost prostřednictvím pipeline, middleware a událostí. Testování přímo ve zdrojovém kódu pomocí atributu #[TestInline] a dalších atributů pro řízení logiky.

Instalace a konfigurace

Instalace přes Composer:

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

V kořenovém adresáři vytvořte soubor testo.php:

<?php

declare(strict_types=1);

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

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

Soubor vrací instanci ApplicationConfig. Pokud chybí, použije se výchozí konfigurace z adresáře tests/. Definujeme dvě suite: Zdroje pro vestavěné testy a benchmarky přímo ve zdrojovém kódu, Testy pro klasické jednotkové testy.

Spouštění: ./vendor/bin/testo nebo přímo z PHPStormu pomocí oficiálního pluginu.

Google AdInline article slot

Ukázky jednotkových testů

Třída bez dědičnosti, metody označené atributem #[Test]:

final class OrderTest
{
    #[Test]
    public function calculatesTotal(): void
    {
        $order = new Order();
        $order->addItem('Kniha', price: 15.0, quantity: 2);
        $order->addItem('Pero', price: 3.0, quantity: 5);

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

    #[Test]
    #[DataSet([100.0, 10, 90.0], 'sleva 10 %')]
    #[DataSet([100.0, 0, 100.0], 'žádná sleva')]
    #[DataSet([0.0, 50, 0.0], 'nulová cena')]
    public function appliesDiscount(float $price, int $percent, float $expected): void
    {
        $result = Order::applyDiscount($price, $percent);

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

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

Atribut #[Test] lze použít i na třídě — pak se všechny metody s návratovým typem void nebo never považují za testy. Funkce mimo třídy se testují bez lifecycle atributů.

Metoda Assert: nejprve skutečná hodnota, poté očekávaná. Podporuje řetězení:

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');

Vestavěné testy a benchmarky

Atribut #[TestInline] ve složce src pro testování privátních metod:

// 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 calculateFinalPrice(
        float $price,
        float $discount,
        float $tax,
    ): float {
        return $price * (1 - $discount) * (1 + $tax);
    }
}

Benchmarky pomocí #[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;
}

Výsledek:

+---+----------+-------+---------+------------------+--------+
| # | Název    | Opakování | Volání | Prům. doba       | RStDev |
+---+----------+-------+---------+------------------+--------+
| 2 | aktuální | 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% |
+---+----------+-------+---------+------------------+--------+

Co je klíčové

  • Nezávislý framework bez jakýchkoli závislostí na PHPUnitu, s plnou podporou pluginů pro přizpůsobení.
  • Vestavěné testy (#[TestInline]) a benchmarky (#[Bench]) přímo ve zdrojovém kódu (src).
  • Typově bezpečné řetězení assertů a čisté atributy bez boilerplate.
  • Konfigurace v PHP souboru s podporou více testovacích sad (suites).
  • Oficiální plugin pro PHPStorm i CLI rozhraní.

— Editorial Team

Advertisement 728x90

Číst dál