Technical Evaluation
Architecture fit
- Dev-only utility. Nothing is loaded in FPM, queue workers, or CLI production paths, so runtime blast radius is effectively zero.
- Fits Laravel's testing stack as configuration rather than code: one
<extensions> entry (PHPUnit ≥9) or <listeners> entry (PHPUnit ≤8) in phpunit.xml, plus an optional config block. No service provider, no facade, no bootstrap changes.
- Non-invasive by design: it consumes per-test durations PHPUnit already collects and sorts them after the run. It does not instrument application code, wrap assertions, or touch the database. Overhead is a post-run sort.
- Addresses a real, recurring TPM problem — "CI takes 22 minutes and nobody knows why" — by converting a diffuse complaint into a ranked offender list.
Integration feasibility
- Composer dev dependency, MIT, 781 stars, single-maintainer project. Install plus configuration is measured in minutes, not days.
- Small configuration surface: threshold in milliseconds, report length, report format, ignore rules for known-slow tests, and per-test annotation overrides. Minimal learning curve.
- The gating question is the PHPUnit major. Last release was 2021-05, targeting PHPUnit 9. PHPUnit 10 reshaped the listener/extension APIs and 11 continued that; Composer may hard-block installation on a Laravel 11 project that ships PHPUnit 10/11.
- It reports; it does not fail builds. Enforcement requires supplementary CI logic (parse output, or gate on JUnit XML durations). That is a scope gap to plan for, not a defect.
Technical risk
- Abandonment risk is medium-to-high (~4 years without a release), but the risk is concentrated at PHPUnit upgrade time and the mitigation is a 50–100 line in-house extension implementing the same output contract. The domain logic is trivial and there is no moat — which is also why the investment case must stay small.
- The dangerous failure mode is silent no-op: if the extension element does not match the installed PHPUnit major, the suite runs green and simply prints nothing. Teams then believe they have slow-test observability when they do not.
- Parallel execution (ParaTest) and process isolation can distort or drop per-test durations. Validate the numbers before using them as a budget basis.
- Supply-chain exposure is low (dev-only, permissive license, no runtime dependencies), though it is still an unaudited package in the build path like any dev tool.
Key questions
- Which PHPUnit major is pinned in
composer.lock, and what does composer why-not say? This determines whether the package installs at all.
- Do we need passive reporting, or a build-failing performance gate?
- Do we run tests in parallel or with process isolation?
- Who owns the slow-test backlog, and what is the acceptable wall-clock budget per pipeline?
- Does platform/security review accept a four-year-stale dev dependency, or do we go straight to the internal equivalent?
Integration Approach
Stack fit
Laravel 8/9 with PHPUnit 9: drop-in. Laravel 10/11 with PHPUnit 10/11: likely incompatible as-is; plan the fork-or-replace path from the start rather than discovering it mid-upgrade.
Migration path
- Phase 1 — baseline.
composer require --dev johnkary/phpunit-speedtrap, register the extension in phpunit.xml, set the threshold to the current 90th-percentile test duration (typically 300–800 ms) so the first run is quiet and credible.
- Phase 2 — visibility. Surface the report in the CI job summary; align local and CI configs so engineers see the same numbers.
- Phase 3 — ratchet. Lower the threshold in steps as offenders are fixed, and curate an ignore list for legitimately heavy integration tests.
- Phase 4 — enforcement (optional). Add a CI gate only after the baseline is stable for a few sprints.
- Phase 5 — contingency. On PHPUnit 10+, either pin, adopt a maintained fork, or ship an internal
PHPUnit\Runner\Extension\Extension with identical output. Keep the output contract stable so CI parsing survives the swap.
Compatibility
Verify Pest compatibility separately if in use — Pest runs atop PHPUnit but extension registration and annotation handling differ. Confirm the PHP version matrix in CI covers whatever PHPUnit 9 supports on the oldest runtime you still test.
Sequencing
Report first, enforce last. A gate built on a noisy baseline gets disabled within two sprints and becomes a net negative.
Operational Impact
Maintenance
Near-zero while on PHPUnit 9 — an XML block and a threshold. The single scheduled maintenance event is the PHPUnit 10/11 upgrade, which is a bounded, predictable fork-or-replace task. Budget half an engineer-day.
Support
Single-maintainer OSS with no SLA. Acceptable precisely because the tool is disposable: losing upstream support costs a morning, not a quarter. Do not build process dependencies on it beyond the output format.
Scaling
Compute cost is negligible; output length is bounded by configuration so it will not flood logs. The scaling constraint is human: with a very large suite the ignore list and threshold need periodic curation or the report degrades into wallpaper nobody reads.
Failure modes
- Silent no-op after a PHPUnit upgrade — mitigate with a deliberately slow canary test in a smoke job that must appear in the report.
- Misleading durations under ParaTest or process isolation.
- Threshold set too low on day one → alert fatigue → the signal is ignored (the most likely real-world failure).
- Dependency hygiene: it gets dropped into
require instead of require-dev, or removed during a "trim dev dependencies" cleanup with no named owner.
Ramp-up
Under half a day for one engineer to install, tune, and document. Effectively zero for the rest of the team, since the output is passive. The real onboarding cost is later: explaining why a given test sits on the ignore list.