How to run PHPUnit tests for changed files only

Do you have a huge project and running all PHPUnit tests takes ages? Here is the way to make your testing faster.

Instead of testing the whole codebase every time you, you might run the tests for the changed files only.

Mind you, it is not completely foolproof - having all tests for changed files green does not mean that all tests for the whole project will still be green! (It should be the case for unit tests, but not necessarily for other test types.)

Also, there are a few assumptions on the way necessary to make it all work as described.

Keeping all that in mind, let's do it!

Modified files list

First of all, we need a list of files that have been modified.

Assuming that we work in a feature branch which is supposed to be merged to develop later on, we can get the list of changed files using:

$ git diff origin/develop --diff-filter=AM --name-only

--diff-filter=AM will return only files that are added (A) or modified (M), while --name-only will show only names of changed files.

Say we are interested only in files from src/ directory:

$ git diff origin/develop --diff-filter=AM --name-only | grep 'src/'

It would be great if we could pass just a list like this to PHPUnit's --filter param, but we can't unofrtunately, as it works with class names only.

Let's then convert those filenames into class names:

$ git diff origin/develop --diff-filter=AM --name-only | grep 'src/' | sed 's/.*\///' | sed 's/\.php//'

And finally merge them into a one-liner:

$ git diff origin/develop --diff-filter=AM --name-only | grep 'src/' | sed 's/.*\///' | sed 's/\.php//' | paste -sd '|' -

Now that's something that we can pass to PHPUnit:

$ bin/phpunit --filter $(git diff origin/develop --diff-filter=AM --name-only | grep 'src/' | sed 's/.*\///' | sed 's/\.php//' | paste -sd '|' -)

Bash/Zsh Alias

But it's much too complex to type it each time we want to run it, so let's just convert it into an alias:

# ~/.aliases

alias utd="bin/phpunit --filter \$(git diff origin/develop --diff-filter=AM --name-only | grep 'src/' | sed 's/.*\///' | sed 's/\.php//' | paste -sd '|' -)"

(utd stands for unit tests diff.)

Note the backslash \ before the whole git diff command - if it wasn't there, the git diff command would be executed when .aliases file is loaded, and not when we call the alias. You will see the same thing used in the other aliases below.

All unit test-related aliases

In my case it is just one of several aliases related to unit testing:

# ~/.aliases

# Unit tests
export PHPUNIT="bin/phpunit --testsuite=Unit -v"
alias changed_files="export CHANGED_FILES=\$(git diff origin/develop --diff-filter=AM --name-only | grep 'src/' | sed 's/.*\///' | sed 's/\.php//' | paste -sd '|' -)"
alias ut="XDEBUG_MODE=off $PHPUNIT"
alias utf="ut --filter"
alias utd="changed_files; utf \$CHANGED_FILES"
# Unit test coverage
alias utc="XDEBUG_MODE=coverage $PHPUNIT --coverage-html=/tmp/coverage"
alias utcf="utc --filter"
alias utcd="changed_files; utcf \$CHANGED_FILES"
alias utco="open /tmp/coverage/index.html"
  • PHPUNIT - base PHPUnit command definition used by other aliases, you might want to update it to adapt it to your project,
  • ut - runs all unit tests
  • utf - runs unit tests allowing to filter by manually provided class name, for example utf ArrayCollectionDenormalizer
  • changed_files - prepares the $CHANGED_FILES env variable, used by other aliases
  • utd - runs unit tests for changed files only (diff)
  • utc - runs all unit tests and generates coverage report
  • utcf - runs all unit tests and generates coverage report, allowing to filter by manually provided class name
  • utcd - runs unit tests and generates coverage report for changed files only (diff)
  • utco - opens the generated coverage report