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ý:
- 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:
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.
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
Zatím žádné komentáře.