GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Testo
  • Docs
  • Blog
  • Русский
  • ...

This view

  • Original page
  • New address

GeezerVison is showing php-testo.github.io — Testo — Modern PHP Testing Framework

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.

Get Started
GitHub
Testo Logo

Clean Code

No TestCase inheritance required — or no classes at all.

Proudly PHP

Because it is a backend language, not it().

IDE Integration

PhpStorm/IntelliJ IDEA plugin with test running, navigation, debugging, and all the workflow you expect.

#[Attributes]

Instead of magic conventions. Minimum boilerplate, maximum expressiveness.

Small Core

Everything else is middleware and event dispatchers with unlimited customization.

Well-Designed API

Separate Assert (now) and Expect (later) facades with pipe assertions for type-safe checks.

Async Friendly

Test leaks, fibers, and async code with built-in tools.

PHP >=8.2

Works on all actively supported PHP versions.

Featured ​

What's Faster
What's Faster

Tempest Testing, PHPUnit and Testo on the same codebase: the test plumbing sets the pace, not the runner.

Beta Testing is Open!
Beta Testing is Open!

Start testing with Testo today and help make it better before the release!

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);
php
use Testo\Expect;

// ORM should stay in memory
Expect::leaks($orm);

// Test fails if entities are not cleaned up
Expect::notLeaks(...$entities);

// Output validation
Expect::output()->contains('Done');
php
// Have you seen this anywhere else?
Expect::exception(ValidationException::class)
    ->fromMethod(Service::class, 'validateInput')
    ->withMessage('Invalid input')
    ->withPrevious(
        WrongTypeException::class,
        static fn (ExpectedException $e) => $e
            ->withCode(42)
            ->withMessage('Field "age" must be integer.'),
    );
php
/**
 * You can use attributes for exception expectations
 */
#[ExpectException(ValidationException::class)]
public function testInvalidInput(): void
{
    $input = ['age' => 'twenty'];

    $this->service->validateInput($input);
}

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);
    }
}
php
// Explicit test with #[Test] attribute
// or "test" prefix in function name

#[Test]
function validates_email_format(): void
{
    $validator = new EmailValidator();

    Assert::true($validator->isValid('[email protected]'));
    Assert::false($validator->isValid('invalid'));
}

function testEmailValidator(): void { ... }
php
// "Test" suffix on class and "test" prefix on methods

final class UserServiceTest
{
    public function testCreatesUser(): void
    {
        $user = $this->service->create('[email protected]');

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

    public function testDeletesUser(): void { /* ... */ }
}
php
// Test the method right in your code
// Convenient for simple cases

final class Calculator
{
    #[TestInline([1, 1], 2)]
    #[TestInline([40, 2], 42)]
    #[TestInline([-5, 5], 0)]
    public function sum(int $a, int $b): int
    {
        return $a + $b;
    }
}

First-Class IDE Integration ​

Native plugin for PhpStorm, IntelliJ IDEA, and OpenIDE.

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

Testo for PhpStorm 4.1 1.1K Testo for OpenIDE

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: ['in cycle' => [self::class, 'sumCycle']],
    arguments: [1, 5_000],
)]
public static function sumArray(int $a, int $b): int
{
    return \array_sum(\range($a, $b));
}
php
// Alternative implementation
public static function sumCycle(int $a, int $b): int
{
    $result = 0;
    for ($i = $a; $i <= $b; ++$i) {
        $result += $i;
    }
    return $result;
}
Results for sumArray:
+--------------------------+------------------------------------------------+---------+
| BENCHMARK SETUP          | TIME RESULTS                                   | SUMMARY |
| Name     | Iters | Calls | Mean              | Median            | RStDev | Place   |
+----------+-------+-------+-------------------+-------------------+--------+---------+
| current  | 10    | 1000  | 11.65µs           | 11.58µs           | ±1.55% | 1st     |
| in cycle | 10    | 1000  | 43.80µs (+275.8%) | 44.17µs (+281.5%) | ±1.67% | 2nd     |
+----------+-------+-------+-------------------+-------------------+--------+---------+

Sponsored by

Buhta
Become a Sponsor|Star on GitHub
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.