Konstantinos Pittas

CTO of Archmule

Running PHPUnit tests in parallel

Recently, I wanted to make my functional/integration (whatever you call them) tests to run faster. So I tried some optimizations that didn’t help a lot. In some cases, there were even some false positive/negative results.

Then I tried to run multiple tests in parallel together. This caused a problem though. All the tests were connecting and migrating/resetting the same database. However, this problem was easy to fix.

I used a PHP library called “fastest”. Even though most of their documentation's examples are about PHP, you could use it for stuff like:

$ ls | fastest "echo slow operation on {}" -vvv

I chose to install it globally to have it at my disposal for any project or occasion, without having to install it as a dependency.

$ composer global install liuggio/fastest

But certainly, you can install it for a specific project only.

$ composer install --dev liuggio/fastest

If you want to use it on your unit tests, you’ll only need this line.

$ find ./tests -name "*Test.php" | fastest "./vendor/bin/phpunit --colors --no-coverage {};"

But if your tests have to touch the database, this will not work. Mostly because when the one test will be adding a row, another one may be adding 10 more rows throwing off your tests that works just fine when running them separately.

One solution (and maybe the easier one) will be to use an in-memory SQLite database. Each database will not affect the other one and your tests will run successfully.

Though I don’t like this approach, as I want my test to hit a real database (MySQL in my case), as it would the same environment with production. But how would I define a different database for each test? Here comes the environment variables fastest provides.

I’m using Laravel, so to change my database configuration, I had to edit the config/database.php file. If you are using Symfony, or some other framework, check the fastest's documentation or your framework's.

So the new MySQL database name will become:

'my_db' . (getenv('ENV_TEST_CHANNEL')
    ? '_' . getenv('ENV_TEST_CHANNEL')
    : '')

I'll be running my test on my (still-rocking) 2012 MacBook Air, which has a Core 2 Duo CPU. So fastest by default (and probably for the best), will run one process per CPU core. So I've created 2 more databases (mydb_testing_1, mydb_testing_2). If you are running your tests on a, for example, 4-core CPU, you will have to add 2 more databases (mydb_testing_3, mydb_testing_4). And so on.


+---------------------+
| Database            |
+---------------------+
| information_schema  |
| mydb                |
| mydb_testing        |
| mydb_testing_1      |
| mydb_testing_2      |
+---------------------+

And that's all. Now my test takes less time to run. Not half the time as you may guess, but I'm happy with it for now.

Also, I've added an alias in my .aliases, to be even "faster":

$ alias ft='find ./tests -name "*Test.php" | fastest "./vendor/bin/phpunit --colors --no-coverage {};"'

Feel free to send me any recommendations for improving it even more at @kostaspt.

← Back home