PHPUnit TIA: Run Only the Tests Your Changes Affect

Jason McCreary just released a PHPUnit extension that ports Pest's TIA Engine over to PHPUnit. Test Impact Analysis records which tests cover which files, then on the next run it only exercises the tests related to the files you actually changed. McCreary reports a 1,000 test suite dropping from 2 minutes to 8 seconds on a replay run.

It works with any PHPUnit 13 suite, in a framework or in plain PHP.

Unimpacted Tests Get Marked Skipped#

Third-party extensions can't change the PHPUnit test runner, so TIA can't pull tests out of the run the way Pest does from the inside. Instead it marks unimpacted tests as skipped, which is the S you'll see in the output. The tests still get collected, they just don't do any work, and that's where the time goes.

One thing to know about: running with --fail-on-skipped or --display-skipped bypasses the speed boost, so you'll want to drop those options to get the benefit.

Setup#

Install it as a dev dependency:

composer require --dev jasonmccreary/phpunit-tia

Then register the extension in your PHPUnit configuration:

<extensions>
<bootstrap class="JMac\Testing\PhpUnit\Tia\Extension">
<parameter name="storage" value="global"/>
</bootstrap>
</extensions>

And add the trait to your base TestCase:

use JMac\Testing\PhpUnit\Tia\Traits\RunWithTia;

abstract class TestCase extends \PHPUnit\Framework\TestCase
{
use RunWithTia;
}

That turns TIA on for every phpunit invocation. The trait supplies its own setUp(), so if your TestCase already declares one, alias the trait's version and call it yourself:

use RunWithTia {
RunWithTia::setUp as tiaSetUp;
}

protected function setUp(): void
{
$this->tiaSetUp();

// ...your own setUp logic
}

You need PHP 8.4, PHPUnit 13.2.6 or newer, and a coverage driver (pcov or Xdebug in coverage mode) so the extension can record new coverage.

Turning It Off and Starting Over#

The baseline gets built automatically on the first run, and two environment variables cover the cases where you want something different. To skip TIA for a run and execute everything:

PHPUNIT_TIA=0 phpunit

To throw out the existing baseline and rebuild it from scratch:

PHPUNIT_TIA_FRESH=1 phpunit

Using It in CI#

TIA diffs against a baseline commit, so its graph has to survive between runs. The repo ships a GitHub Action workflow you can copy, and the pieces that matter are:

Check out with full git history (fetch-depth: 0)

Re-attach HEAD to the real branch name, since a detached HEAD collapses baselines across branches

Cache the storage directory (~/.phpunit-tia for global storage, or your configured path for local) keyed per branch and runner, and save it after every run

McCreary's own note is that TIA is meant to shorten the feedback loop while you're working, and that running the full suite in CI is still the right call.

Visit the GitHub repo for the full setup instructions and the example workflow.