Musings of a startup founder

Musings of a startup founder


Chris Duell, co-founder @ elev.io

Chris Duell
Author

CEO & Co-founder of elev.io

Share


elevio

Twitter


Speeding up PHPUnit tests 15 times

Chris DuellChris Duell

Testing is still something I feel I need to almost force myself to do, and what previously made it worse was that my tests took so long to run. While working on elev.io development for to create a better way for you to support your users (seriously, check it out and support what I do), while we only had 50% code coverage, those tests already took over 35 seconds to complete.

It was enough for me to no longer bother consistently running tests, which almost defeats the purpose of self testing code. Until I got fed up enough to spend the time on speeding up the tests, by fifteen times (and almost halving memory usage).

Here’s what I did.

This post is somewhat heavy on laravel, however the end goal is applicable for any PHP unit testing

Why was it so slow to begin with?

First I’ll clarify why my tests were so slow, for all of my acceptance tests I wanted the full page to render, which meant that I couldn’t sanely mock every possible item that needed to be pulled from the database. So, for each and every test that needed data from the database, I needed to setup and seed the database. The project I was working on was using laravel, so I was doing something like this:

public function setupDatabase() { Artisan::call('migrate'); Artisan::call('db:seed'); $this->migrated = true; }

And I would call this function before each of the tests that needed a database (every test needs to be run on a fresh, known database state that hasn’t been tainted by other tests). Just this migration and seeding process took over a second each time, which when you start building up a large test suite really starts to add up. 

The initial test database setup

Like a lot of people, I was putting the database in memory rather having to rely on creating an actual database on disk with mysql. I’d seen this technique being used and recommended quite a lot, so figured it was just the done thing so put up with it for a while.

The new test database setup

After getting fed up enough to find a faster way to setup and destroy the database for each and every test that needed it, I started to look at alternate database types. And one stood out, big time. sqlite being a flat file meant that I could simply have a prebuilt and seeded database I could just copy that file and run a test on it then trash the file. Then just rinse and repeat for each test I needed a database for.

This way, instead of having to migrate a whole database, then fill it with data, all I needed to do was copy a single sqlite file.

Since I was using laravel, I setup my app/testing/database.php file with the following:

'testing', 'connections' => array( 'setup' => array( 'driver' => 'sqlite', 'database' => __DIR__.'/../../database/testing/stubdb.sqlite', 'prefix' => '', ), 'testing' => array( 'driver' => 'sqlite', 'database' => __DIR__.'/../../database/testing/testdb.sqlite', 'prefix' => '', ), 'sqlite' => array( 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '' ), ), 'migrations' => 'migrations', ); You’ll notice there’s two items that are using sqlite, this is so I can easily create a new stub file if my schema or seed data changes. Using laravels artisan tool, I can run the following to regenerate my stub database: php artisan migrate:refresh --seed --database="setup" --env="testing" > NB the first time you setup your stub file, you’ll need to manually create the stubdb.sqlite file, and run a normal migration withouth the “refresh” So now, when I run my tests, instead of calling the migration and seed commands, I simply copy the stub database file like this: public function setupDatabase() { exec('rm ' . __DIR__ . '/../database/testing/testdb.sqlite'); exec('cp ' . __DIR__ . '/../database/testing/stubdb.sqlite ' . __DIR__ . '/../database/testing/testdb.sqlite'); } ## Bonus: Auto updating the seed database file If you’re not using something like [grunt](http://gruntjs.com/) or [gulp](http://gulpjs.com/) already, get on it. Seriously. They are amazing for optimizing your workflow. I’m using gulp since I like it’s format a lot more, it just seems a lot more readable and easier to work with. Gulp comes in really handy by watching certain files for any updates, and acting according to rules that you setup. So you can tell it to check for any php files that are updated in your main apps folder, and automatically run your tests. Or you could tell it to watch for updates to any css / less files being updated, and compile and compress them into a single minified file immediately, and also reload the browser to show your changes. There’s so much you can do, but that’s for another post. What I’ve also decided do with grunt, is to check for any updates to the laravel migration or seed files, and as soon as I make a change to those it will automatically call the above migration command to recreate the stub database file. Testing just got a whole lot more accessible, and sane, for me. ## Edit & final note A few people have mention in the comments that this isn’t pure unit testing, and they are 100% correct. But I’m OK with that, myself and a lot of other people don’t care if the testing is ‘pure’, as long as we’re testing. Should I be more anal about testing, yes, of course. But for now, this approach keeps me testing.
Chris Duell
Author

Chris Duell

CEO & Co-founder of elev.io

Comments