Running Testo tests
SkillFiles & storageRun tests with the Testo CLI and act on the result. Use when executing a project's test suite or a subset (one test, one file, one suite, a group), when verifying a change didn't break tests, or when parsing a Testo run report. Triggers: "run the tests", "does it pass", "run only X", "re-run the failing test".
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Running Testo tests skill
What this skill tells your AI
The instructions your AI receives, as published by php-testo/testo in skills/testo-run-tests/SKILL.md and read by ahel’s review.
Always run through the Testo CLI, and always with --json:
vendor/bin/testo --json
--json replaces the human progress stream with one compact JSON object on stdout: a run
summary plus the failed tests only. Passing, skipped, and risky tests collapse into counts, so
even a large suite costs almost no context. The human-readable terminal output is not a stable
interface — parse the JSON. When a human also needs terminal output (CI logs), use
--log-json=build/report.json instead: same JSON to a file, terminal output kept.
Reading the report
{
"status": "failed",
"duration": 1.42,
"totals": {"total": 120, "passed": 118, "failed": 1, "error": 1},
"failures": [
{
"test": "App\\Tests\\UserServiceTest::createsUser",
"status": "failed",
"exception": "Testo\\Assert\\AssertionFailedError",
"message": "...",
"file": "/app/tests/UserServiceTest.php",
"line": 42,
"trace": ["#0 /app/tests/UserServiceTest.php:42: ..."],
"causedBy": [{"exception": "...", "message": "...", "file": "...", "line": 1}],
"output": [{"channel": "stdout", "content": "..."}]
}
]
}
status— the run verdict. Gate on"status": "passed", not onfailuresbeing empty: a run can be risky with zero failures.totals— counts keyed by lowercased test status (passed,failed,error,skipped,risky,flaky,cancelled,aborted); zero counts are omitted.failures— every failed/errored test with what you need to fix it: the throwable, itspreviouschain (causedBy), a stack trace trimmed at the test boundary, and captured output (stdout, log channels).benchmarks— present only when the run measured benchmarks (#[Bench]), omitted otherwise, so an ordinary run's payload is unchanged. One entry per benchmark test — per data set for a repeatable one, each carrying itsdataProvider/dataSet— withiterations, the rankedcases, anddiagnostics.
"benchmarks": [
{
"test": "App\\Bench\\SortBench::compare",
"iterations": 10,
"cases": [
{"name": "shift", "place": 1, "calls": 20, "mean": 0.26, "median": 0.27, "meanDiff": 0.0,
"medianDiff": 0.0, "rstdev": 5.6, "filteredMean": 0.26, "filteredMeanDiff": 0.0,
"filteredRstdev": 5.6, "rejected": 0, "memory": 4096}
],
"diagnostics": [{"case": "shift", "kind": "HighVariance", "severity": "warning", "reason": "…", "advice": "…"}]
}
]
A case ranks by place (1 is fastest); times are microseconds, each …Diff a percentage against the
current case (the #[Bench]-marked method) as the baseline — 0.0 for it, positive for slower,
negative for faster — rstdev the spread as a percent of the mean, the filtered* trio the same with
outliers dropped, and memory the peak bytes per iteration.
Selecting what to run
Every filter is repeatable; different filters combine, each narrowing further:
vendor/bin/testo --json --suite=Unit # one suite (name from testo.php)
vendor/bin/testo --json --filter=UserServiceTest # by name fragment
vendor/bin/testo --json --filter='UserServiceTest::createsUser' # one test method
vendor/bin/testo --json --path=tests/Unit/UserServiceTest.php # one file
vendor/bin/testo --json --path=tests/Unit/Service # one directory (no globs)
vendor/bin/testo --json --group=db --group=!slow # groups: OR to include, ! to exclude
vendor/bin/testo --json --type=!bench # types: test, inline, bench, profile
--filteracceptsClass::method, a FQN (class or function), or a bare fragment (method name, function name, or short class name).--groupmatches#[Group('name')];--typematches how the test is declared —test(#[Test]),inline(#[TestInline]),bench,profile. For both, values OR together and a!-prefixed value excludes; exclusion wins over inclusion.- If the project has
#[Bench]benchmarks, they are slow — add--type=!benchunless the task is benchmarking (see thetesto-benchmarksskill).
Exit code and the empty run
The exit code is 0 only for a passed run. A run that discovers zero tests is not green:
it reports "status": "risky" with "total": 0 and exits non-zero. The usual cause is an
over-narrow filter — widen it (a typo in --filter, a --path that matches nothing, a
--suite name that doesn't exist in testo.php).
Beyond a plain run
- Coverage (
--coverage,--coverage-clover=,--coverage-level=, …) — see thetesto-coverageskill. --log-junit=,--log-html=(a.htmlpath → single file, anything else → directory; bare--log-html→runtime/report),--log-report=(the full run as a versioned JSON document — the data behind the HTML),--teamcity— reports for CI and IDEs, not for agent parsing.--config=path/to/testo.phpwhen the config is not at the project root.- Full flag list:
vendor/bin/testo --help.
Signals
- GitHub stars
- 213
- Forks
- 16
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
testo-run-tests- Source
- github.com/php-testo/testo