Skip to content
...

The PHP Testing Framework with a Taste for Khinkali

Built for developers, who crave bread-and-butter tests with juicy environment control.

Testo Logo

🚧 Work in Progress

Testo is still under active development and not ready for production use. Feel free to explore and experiment, but don't rely on it for real projects yet.

Want to support the project? Star the repo or become a sponsor.

Well-Designed Assertion API

Assertion functions are split into semantic groups:

  • Assert\Testo\Assert facade — assertions, executed immediately
  • Expect\Testo\Expect facade — expectations, deferred until test completion

Pipe syntax with type grouping keeps code concise and type-safe.

php
use Testo\Assert;

// Pipe assertions — grouped by type
Assert::string($email)->contains('@');
Assert::int($age)->greaterThan(18);
Assert::file('config.php')->exists();

Assert::array($order->items)
    ->allOf(Item::class)
    ->hasCount(3);

Multiple Ways to Declare Tests

Write tests the way that fits your style.

  • Tests can be classes, functions, or even attributes right in production code (Inline Tests).
  • Classes don't need to inherit from a base test class. Code stays clean.
  • Test discovery by naming conventions or explicit attributes.
php
// Explicit test declaration with #[Test] attribute

final class OrderTest
{
    #[Test]
    public function createsOrderWithItems(): void
    {
        $order = new Order();
        $order->addItem(new Product('Bread'));

        Assert::int($order->itemCount())->equals(1);
    }
}

First-Class IDE Integration

Native plugin for PhpStorm and IntelliJ IDEA.

Full-featured workflow: run and re-run from gutter icons, navigation between tests and code, debugging with breakpoints, test generation, results tree.

Get Plugin 4.1 268

Benchmarks with a Single Attribute

Add the #[Bench]#[Bench(array $callables, array $arguments = [], int $warmup = 1, int $calls = 1_000, int $iterations = 10)]Declares a benchmark comparing the method's performance against alternative implementations. attribute to a method, and Testo will show which implementation is faster. With statistics, outlier filtering, and stability recommendations.

php
// Baseline method with the #[Bench] attribute
#[Bench(
    callables: [ 'sumInCycle' => [self::class, 'sumInCycle']],
    arguments: [1, 5_000],
)]
public static function sumInArray(int $a, int $b): int
{
    return \array_sum(\range($a, $b));
}
php
// Alternative implementation
public static function sumInCycle(int $a, int $b): int
{
    $result = 0;
    for ($i = $a; $i <= $b; ++$i) {
        $result += $i;
    }
    return $result;
}