Zurück zur Startseite

Testo-Framework: PHP-Tests und Benchmarks

Testo — unabhängiges Framework für Unit-Tests und Benchmarks in PHP 8.2+. Unterstützt Plugins, Inline-Tests in src, typed Assert. Installation via Composer, Code-Beispiele für Middle-/Senior-Entwickler.

Testo: neues Test-Framework für PHP 8.2+
Advertisement 728x90

Testo: Ein eigenständiges PHP-Test- und Benchmarking-Framework

Das neue Testo-Framework steht nun für Beta-Tests zur Verfügung. Es handelt sich um eine abhängigkeitsfreie Implementierung – ohne Abhängigkeit von PHPUnit – mit Unterstützung für PHP 8.2+, Plugins, Inline-Tests und Benchmarks. Speziell für mittlere bis erfahrene PHP-Entwickler konzipiert, legt Testo Wert auf Flexibilität, strikte Typisierung und minimale Boilerplate.

Wichtige Unterschiede zu PHPUnit und Alternativen

Testo wurde entwickelt, um zentrale Schwächen von PHPUnit zu beheben: mangelnde Flexibilität, häufige Breaking Changes und Stillstand jenseits der grundlegenden PHP-Version-Unterstützung. Der Autor war im Issue-Tracker und bei Pull Requests von PHPUnit aktiv beteiligt – doch vorgeschlagene Verbesserungen wurden nicht übernommen. PEST und Codeception erben die Einschränkungen von PHPUnit und fügen zudem umständliche Syntax hinzu.

Testo ist vollständig unabhängig:

Google AdInline article slot
  • Vermeidet nikic/php-parser und eliminiert damit parserbezogene Konflikte.
  • Setzt PHP 8.2+ voraus.
  • KI-Agenten können automatisch Tests anhand von llms.txt generieren – einer strukturierten Spezifikation, die aus der offiziellen Dokumentation abgeleitet ist.

Ein modulares Plugin-System ermöglicht projektspezifische Anpassungen. Jedes Feature wird als optionales Plugin ausgeliefert; jede Test-Suite wählt ihre eigene Kombination aus. So fügt das #[Retry]-Plugin beispielsweise Resilienz gegen flaky Tests hinzu – realisiert mit nur zwei schlanken Dateien.

Erweitern Sie das Verhalten über Pipelines, Middleware und Events. Schreiben Sie Inline-Tests direkt in Ihrem src/-Code mit #[TestInline] und nutzen Sie Attribute, um Testlogik sauber einzubetten.

Installation und Konfiguration

Installieren Sie Testo über Composer:

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

Erstellen Sie testo.php im Projektstammverzeichnis:

<?php

declare(strict_types=1);

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

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

Diese Datei gibt eine Instanz von ApplicationConfig zurück. Ohne sie greift Testo auf Standardwerte zurück (z. B. Scan des tests/-Verzeichnisses). Zwei Standard-Suites sind vordefiniert: Sources für Inline-Tests und Benchmarks innerhalb des Produktionscodes sowie Tests für klassische Unit-Tests.

Führen Sie Tests mit ./vendor/bin/testo aus – oder nutzen Sie das offizielle PHPStorm-Plugin für nahtlose IDE-Integration.

Google AdInline article slot

Beispiele für Unit-Tests

Keine Vererbung nötig. Markieren Sie Methoden einfach mit #[Test]:

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

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

    #[Test]
    #[DataSet([100.0, 10, 90.0], '10 % Rabatt')]
    #[DataSet([100.0, 0, 100.0], 'kein Rabatt')]
    #[DataSet([0.0, 50, 0.0], 'Nullpreis')]
    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);
    }
}

Mit #[Test] am Klassennamen werden alle Methoden mit Rückgabetyp void oder never automatisch als Tests behandelt. Eigenständige Funktionen (außerhalb von Klassen) laufen ohne Lifecycle-Attribute.

Assertions folgen einer intuitiven Reihenfolge: tatsächlicher Wert zuerst, erwarteter Wert zweitens. Kettenbare, typsichere Assertions:

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

Inline-Tests und Benchmarks

Nutzen Sie #[TestInline] innerhalb von src/, um private oder interne Methoden zu prüfen:

// 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);
    }
}

Messen Sie Performance mit #[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;
}

Beispielausgabe:

+---+----------+-------+---------+------------------+--------+
| # | Name     | Aufrufe | Calls   | Ø Zeit           | RStDev |
+---+----------+-------+---------+------------------+--------+
| 2 | aktuell  | 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% |
+---+----------+-------+---------+------------------+--------+

Warum Testo relevant ist

  • Ein wirklich eigenständiges Framework – keine PHPUnit-Abhängigkeiten, erweiterbar über komponierbare Plugins.
  • Inline-Tests (#[TestInline]) und Benchmarks (#[Bench]) leben direkt neben dem Produktionscode in src/.
  • Typsichere, fluide Assertion-Ketten – keine Boilerplate, keine Vererbung, keine magischen Globals.
  • Konfiguration über reine PHP-Dateien mit vollem Support für Multi-Suite-Projekte.
  • Erstklassiges PHPStorm-Plugin und CLI-Tooling – bereit für den täglichen Einsatz.

— Editorial Team

Advertisement 728x90

Weiterlesen