Other Speed Tips
TIP
Do you have any other useful tips to speed up tests? Please Let me know!.
Use PHPUnit's TestCase
If a test doesn't need any Laravel functionality, extend from PHPUnit\Framework\TestCase
instead of Tests\TestCase
. It's much quicker.
Don't Use a Database
Not all tests need a database, so skip the database altogether when it's not needed!
This is the easiest optimisation to make, and it might seem obvious. But it's worth mentioning, so it's not overlooked.
Use Memory Databases
There are a few different ways to run your test-databases in memory, depending on the type of database you use.
TIP
When using Adapt, you can use the timing details shown in the logs to get a better idea of what's really fastest for your tests.
SQLite Memory Databases
WARNING
If your project normally uses MySQL or PostgreSQL, you should consider testing against those at least somewhere in your development process.
While it's often possible to run tests with a SQLite database, SQLite isn't fully compatible with MySQL and PostgreSQL.
- The article Stop Using Sqlite in Laravel Unit Tests describes some subtle differences that can cause tests to pass when using SQLite, that will fail when using MySQL.
- SQLite's Quirks, Caveats, and Gotchas In SQLite page lists unexpected things you may find.
When using SQLite, you have the option of using a database in memory. Just change the name of the database from a file to :memory:
.
# .env.testing
DB_DATABASE=:memory:
However, whilst it might be attractive to do this, it might not be the fastest option.
SQLite memory databases only exist for the current connection, and disappear straight away after disconnection. Because Laravel disconnects from its databases after each test, these databases need to be rebuilt each time.
It depends on your particular tests, but it will probably be quicker to have Adapt reuse file-based SQLite databases.
Instead of using a :memory:
database, another way to approach this problem is to look at using a memory filesystem for these databases.
These run approximately as fast, allow for databases to be re-used, and also allow for the database files to be copied (which Adapt takes advantage of when snapshots are on).
TIP
Linux has several memory-based filesystems that you could look at using. macOS and Windows have options available as well.
If you're using Docker, you can add a memory tmpfs (memory) filesystem like this:
# docker-compose.yml
version: '3'
services:
php:
build:
context: './build/php'
tmpfs:
- /var/www/html/database/adapt-test-storage
environment:
…
NOTE
By default this directory will be owned by root
inside your container. Most likely you will need to address ownership before Adapt can write to it.
MySQL & PostgreSQL Memory Databases
When using MySQL or PostgreSQL, it's possible to run your test-database server with a memory filesystem.
The database server will run like normal, but it will run faster because the location where it stores its data is in memory. Databases will persist until the server is restarted, so Adapt will be able to reuse them between test-runs.
TIP
Linux has several memory-based filesystems that you could look at using. macOS and Windows have options available as well.
If you're using Docker, add a new MySQL container with a tmpfs (memory) filesystem for your tests:
# docker-compose.yml
version: '3'
services:
mysql-tests:
image: mysql:8.0
tmpfs:
- /var/lib/mysql
environment:
…
And the same for PostgreSQL:
# docker-compose.yml
version: '3'
services:
postgresql-tests:
image: postgres:14
tmpfs:
- /var/lib/postgresql/data
environment:
…
Migrations
Squash Migrations
Laravel 8 introduced the ability to squash migrations into a sql-dump file. When migrating from scratch, Laravel imports this first before running any newer migrations.
This would be a good idea if you have lots of migrations that update existing tables.
Seeders
Bulk Insert Seeder Data
Rows that are inserted individually will run slower than those combined into a single query. Combining the inserts will reduce the number of instructions sent to the database, which saves time.
# slower
INSERT INTO users ('name', 'email') VALUES ('bob', 'bob@example.com');
INSERT INTO users ('name', 'email') VALUES ('jane', 'jane@example.com');
…
# faster
INSERT INTO users ('name', 'email') VALUES
('bob', 'bob@example.com'), ('jane', 'jane@example.com'), …;
Laravel factories can be used in this way too:
// instead of inserting rows in separate queries
User::factory()->count(1000)->create();
// insert all the rows in a single query
User::insert(
User::factory()->count(1000)->make()->toArray()
);
Wrap Seeders in a Transaction
Wrapping insert queries in a transaction may give you a speed increase.
<?php
// database/seeders/DatabaseSeeder.php
…
class DatabaseSeeder extends Seeder
{
public function run()
{
DB::beginTransaction();
$this->call(UsersSeeder::class);
…
DB::commit();
}
}
Disable Foreign Key Constraints
In MySQL, when tables use foreign keys, inserting will be quicker when FOREIGN_KEY_CHECKS
is turned off. This will disable the extra checks MySQL needs to make. Just remember to turn it on afterwards.
<?php
// database/seeders/DatabaseSeeder.php
…
class DatabaseSeeder extends Seeder
{
public function run()
{
DB::statement("SET FOREIGN_KEY_CHECKS=0");
$this->call(UsersSeeder::class);
…
DB::statement("SET FOREIGN_KEY_CHECKS=1");
}
}
Reduce Password Hash Rounds
Password hashing is designed to be slow. Reducing the number of iterations used will speed this up.
Recent versions of Laravel already apply this for you in phpunit.xml
// phpunit.xml
…
<env name="BCRYPT_ROUNDS" value="4"/>
…
TIP
If your seeders hash a lot of passwords, you could consider simply hard-coding them instead.
Use Mocks
Depending on what you're trying to test, it can be useful to mock certain functionality in your tests.
Run Specific Tests
When working on a particular piece of code, adding --filter=xxx
option as you run your tests will help you by only running the relevant tests. Class names and test-methods can be searched for this way.
Re-run Failed Tests
Running only failed tests might help you focus on the problem you're currently working on. PHPUnit 7.3 introduced the ability to do this.
Use PHPUnit Directly
Running your tests via PHPUnit directly ./vendor/bin/phpunit
is slightly faster than using Laravel's php artisan test
command. However, the difference is small.
Disable Xdebug, Use PCov
Xdebug is a useful development tool. It performs a range of tasks, one of them is to monitor code-coverage. This is needed if you want your test suite to generate code-coverage reports.
However, it adds overhead as your code runs, and will slow down your tests. Disabling Xdebug will give your tests a speed boost.
If you'd still like to generate code-coverage reports, try PCov instead as it's much faster. It focuses on code-coverage, without providing the other functionality that Xdebug does.
Support for PCov was added in PHPUnit 8 - if you're using an earlier version you could look at using \pcov\Clobber.
TIP
If you use Dockerised containers in your project, you might like to consider having a separate container for testing. This way you can still use Xdebug in your development environment, but your tests can run without it.
Use SSD Storage
SSD storage is a lot faster than older HDD drives (with spinning disk platters). Upgrading to SSD drives may give you a speed boost, particularly if the code you're testing accesses the filesystem a lot.
See the troubleshooting section for more steps you can take when using a HDD.