It is my fervent wish that this file guide every AI coding agent working with code in this repository.
Any distilled, agent-facing documentation for this package - how it works
internally and the rationale behind key design decisions - lives in docs/.
Consult it before non-trivial changes; it is the source of truth from which the
public manual is distilled.
This repo is the implementation of Tester (not a guide to using it). It has two
seams - the in-process framework (assertions, TestCase, Environment) and the
out-of-process runner (orchestration, isolation, annotation dispatch) - bridged by
process exit codes and env vars. Read docs/internals.md before editing either.
Nette Tester is a lightweight, zero-dependency PHP testing framework. Each test runs in a fully isolated PHP process; tests run in parallel by default (8 threads), are annotation-driven, and the framework is self-hosting (it tests itself).
- PHP Version: 8.0–8.5 (composer upper bound)
- Package:
nette/tester
# Run the suite with the in-repo runner (not vendor/bin)
src/tester tests -s
src/tester tests/Framework/Assert.same.phpt -s
# A single test is a plain runnable script
php tests/Framework/Assert.same.phpt
# Static analysis
composer phpstanUseful runner flags: -j <n> (parallel jobs; -j 1 = serial for debugging),
-o <format> (console/console-lines/tap/junit/log/none), -C/-c/-d (php.ini),
--watch, --stop-on-fail, --cider (hidden, not in --help: live per-thread
panel; needs -j >= 2, otherwise falls back to console-lines).
- Every file starts with
declare(strict_types=1);; tabs; Nette Coding Standard, but PSR-12 same-line braces here (return type and opening brace on the same line - unlike most Nette packages). - Tests are
.phpt(auto-discovered) or*Test.php;.phptxis deliberately NOT auto-discovered (used in Tester's own suite for fixtures). There is no autoloading -tester.phploads classes manually to avoid autoloader conflicts.
AssertExceptionis deliberately catchable, and this is the single most important invariant.Assert::notEqualcatches it to invert anExpectcomparison,TestCase::runTestcatches it to decorate the failure message,TestCase::runand thetest()helper catch (and rethrow) so aTestCasecontinues after one method fails andtest()can print its × marker. Never turnAssert::fail()into a hardexit.Assert::$counterincrements at the top of every assertion, before the check (it counts attempts). It backs the "this test forgets to execute an assertion" guard, whichsetup()disables for@outputMatch/@outputMatchFiletests.fail()throws during the test body but delegates to$onFailureonceEnvironmentinstalls it in a shutdown handler.match()uses PCRE only when the pattern is delimited with~or#(isPcre); otherwise it's a wildcard mask (%a%,%A%,%d%,%h%, ...). Keep the two branches distinct - the delimiter is the only signal.- The runner reads annotations statically (regex over the source) before spawning
the process, then dispatches by reflection to
initiate*/assess*methods. Consequences: file-level annotations cannot be replaced by PHP attributes, and adding a new annotation means adding a method.@testCasefans out in two phases (listtest*methods, then one job per method); the method list is cached in temp and invalidated by mtimes of the class + parents + traits files. FileMutator(bypassFinals) registers itself as thefile://stream wrapper; itsnative()must restore the original wrapper and re-register itself in afinally, or every internal fs call recurses. Preserve that dance.Environment::print()bypasses the ANSI-strippingob_startbuffer (writes straight to STDOUT), which is why status/fatal lines always appear.- The CLI help text IS the option grammar.
CommandLineparses option names, arguments, enums and defaults out of the usage string ((default: 8)is where-j's default lives), so rewording--helpcan change parsing; hidden options (--debug,--cider, ...) exist only in the$defaultsarray. - Jobs are ordered by the previous run's result (cache in temp): new tests first, then failed, passed, and skipped last.
- User-facing how-to (writing tests, the assertion/annotation catalog, HttpAssert, DomQuery, FileMock, coverage usage) is manual material and lives in the web docs.