The PHP Testing Framework You Control
Testo is an extensible testing framework built on a lightweight core with a middleware system. It gives you full control over your testing environment while keeping the familiar PHP syntax you already know.
composer require --dev testo/testo *The fastest way to set up Testo in your project is the built-in init command:
vendor/bin/testo initIt will:
- detect your
src/directory (or prompt for it), - create
tests/Unit/if missing, - generate a minimal
testo.phpnext to yourcomposer.json, - register
composer testandcomposer test:<suite>scripts.
For a sub-app layout, point it at the project root: vendor/bin/testo init --path=app.
testo.php is plain PHP returning an ApplicationConfig — edit it freely to add suites, plugins, or coverage. A typical setup looks like:
<?php
declare(strict_types=1);
use Testo\Application\Config\ApplicationConfig;
use Testo\Application\Config\Plugin\SuitePlugins;
use Testo\Application\Config\SuiteConfig;
use Testo\Bench\BenchmarkPlugin;
use Testo\Inline\InlineTestPlugin;
return new ApplicationConfig(
src: ['src'],
suites: [
new SuiteConfig(
name: 'Sources',
location: ['src'],
// Only Benchmarking and Inline Tests for Source files
plugins: SuitePlugins::only(
new InlineTestPlugin(),
new BenchmarkPlugin(),
),
),
new SuiteConfig(
name: 'Unit',
location: ['tests/Unit'],
),
],
);If no testo.php is present at all, Testo falls back to running every test under the tests/ folder.
To run your tests, execute:
composer test…or call the binary directly if you skipped init / didn't register the script:
vendor/bin/testoYou can also run a single suite by name (one script per detected suite is registered by init):
composer test:unit
composer test:integrationCreate a test class in the configured test directory (e.g., tests/CalculatorTest.php):
<?php
declare(strict_types=1);
namespace Tests;
use Testo\Assert;
use Testo\Assert\ExpectException;
use Testo\Retry;
use Testo\Test;
#[Test]
final class CalculatorTest
{
public function dividesNumbers(): void
{
$result = 10 / 2;
Assert::same($result, 5.0);
Assert::notSame($result, 5); // Types matter!
}
#[Retry(maxAttempts: 3)]
public function flakyApiCall(): void
{
// Retries up to 3 times if test fails
$response = $this->makeExternalApiCall();
Assert::same($response->status, 200);
}
#[ExpectException(\RuntimeException::class)]
public function throwsException(): void
{
throw new \RuntimeException('Expected error');
}
}What to note:
- Use the
#[Test]attribute to mark test methods or classes - Test classes don't need to extend any base class
- Use
Assertclass for assertions (same,true,false,null,contains,instanceOf, etc.) - Testo provides multiple attributes to extend testing capabilities (retry policies, exception handling, and more)
Testo comes with the IDEA plugin Testo.