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:
<?phpdeclare(strict_types=1);
useTesto\Application\Config\ApplicationConfig;
useTesto\Application\Config\Plugin\SuitePlugins;
useTesto\Application\Config\SuiteConfig;
useTesto\Bench\BenchmarkPlugin;
useTesto\Inline\InlineTestPlugin;
returnnewApplicationConfig(
src: ['src'],
suites: [
newSuiteConfig(
name: 'Sources',
location: ['src'],
// Only Benchmarking and Inline Tests for Source files
plugins: SuitePlugins::only(
newInlineTestPlugin(),
newBenchmarkPlugin(),
),
),
newSuiteConfig(
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):
<?phpdeclare(strict_types=1);
namespaceTests;
useTesto\Assert;
useTesto\Assert\ExpectException;
useTesto\Retry;
useTesto\Test;
#[Test]
finalclass CalculatorTest
{
publicfunctiondividesNumbers(): void
{
$result = 10 / 2;
Assert::same($result, 5.0);
Assert::notSame($result, 5); // Types matter!
}
#[Retry(maxAttempts: 3)]
publicfunctionflakyApiCall(): void
{
// Retries up to 3 times if test fails$response = $this->makeExternalApiCall();
Assert::same($response->status, 200);
}
#[ExpectException(\RuntimeException::class)]
publicfunctionthrowsException(): void
{
thrownew \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.