skip to content

A Laravel subscription-box app's test suite takes twenty minutes; how would you use php artisan test --profile and --parallel to shorten it?

level: seniorimportance: must knowfreq 40%

answer

  1. measure before parallelizing
  2. ten slowest tests
  3. brianium/paratest, one process per core
  4. database name suffixed _test_{token}
  5. --processes and --recreate-databases

basics

~20 s

Profile first with php artisan test --profile to find the slowest tests and fix their causes, then install brianium/paratest and run php artisan test --parallel, which gives each process its own database, cache prefix and compiled-view folder.

solid answer

~40 s

I would measure before changing anything: `php artisan test --profile` lists the ten slowest tests, and slow tests usually share a cause such as real network calls, sleeps, heavy seeding or needless full resets. After fixing those, I add `brianium/paratest` as a dev dependency and run `php artisan test --parallel`, which starts one process per CPU core by default (`--processes=4` to tune). For tests using a database reset trait, Laravel creates and migrates a database per process, such as `boxes_test_1` and `boxes_test_2`, and keeps them between runs unless you pass `--recreate-databases`. It also gives each process its own cache prefix and compiled Blade view folder. The database user needs permission to create databases, and tests that share state outside those resources, or depend on run order, will start failing and must be fixed.

code

bash · 10 lines
bash
# 1. find the slowest ten tests
php artisan test --profile

# 2. enable parallel runs
composer require brianium/paratest --dev
php artisan test --parallel --processes=4
# workers use boxes_test_1 ... boxes_test_4

# 3. after migrations changed, rebuild the per-process databases
php artisan test --parallel --recreate-databases

go deeper

for a junior

Know that php artisan test can run in parallel with --parallel and that --profile shows the slowest tests.

for a middle

Explain that paratest is required, that processes default to the CPU count, and that each process gets a database suffixed with its token.

for a senior

Lead with measurement, then parallelize; anticipate create-database privileges, order-dependent tests and shared files or Redis that Laravel does not isolate.

for a principal

Weigh suite speed against CI cost and flakiness: cores per runner, which tests belong in the fast lane, and a budget that keeps the suite under a target time.

## Measure first A twenty-minute suite is rarely slow evenly. The first move is to find where the time goes: ```bash php artisan test --profile ``` `--profile` prints the **ten slowest tests** after the run. In a subscription-box app the usual suspects are: - tests that call a real payment or shipping API instead of a fake; - `sleep()` calls in retry logic that tests never replaced; - seeding a large catalogue of boxes and products before every test; - a reset strategy that rebuilds the whole schema per test instead of rolling back a transaction; - pure-logic tests that extend `Tests\TestCase` and pay for a full application boot they do not need. The skeleton already helps with one classic cost: `phpunit.xml` sets `BCRYPT_ROUNDS=4`, so hashing passwords in factories is cheap. Laravel also offers a `WithCachedConfig` testing trait that builds the configuration once per run instead of loading every config file for each test. Fixing the top ten often buys more than adding cores does, and it makes the parallel run faster too. ## Turning on parallel runs ```bash composer require brianium/paratest --dev php artisan test --parallel php artisan test --parallel --processes=4 ``` By default, Pest and PHPUnit run every test **sequentially in one process**. With `--parallel`, Laravel hands the suite to paratest, which starts **as many processes as the machine has CPU cores** unless `--processes` says otherwise. Each worker gets a numeric **token** (1, 2, 3 ...) that Laravel uses to keep workers apart. ## What Laravel isolates for you Laravel's parallel-testing service provider registers hooks that run for every test case in a parallel run: | Resource | What each process gets | |---|---| | Database | its own database named `{name}_test_{token}`, e.g. `boxes_test_3` | | Cache | a cache prefix extended with `test_{token}_` | | Compiled Blade views | a `test_{token}` subfolder of the compiled-view path | The database part applies when the test class uses a database reset trait and the connection is not SQLite `:memory:`. For an in-memory database each process already has its own private database, so nothing needs creating. For MySQL or PostgreSQL, Laravel checks whether the per-process database exists and creates it when it does not, then switches the default connection's database name (or rewrites the path of a connection URL). Test databases **persist between runs** so the next run can reuse them. After schema changes that leave them in a bad state, `--recreate-databases` drops and recreates them. ## What breaks when you parallelize Parallel runs expose problems that sequential runs hid: 1. **Missing privileges.** Creating `boxes_test_1` needs a database user allowed to create databases; a locked-down CI user fails on the first worker. 2. **Order dependence.** A test that only passed because an earlier test created a subscriber now runs in a different process and fails. 3. **Shared resources Laravel does not isolate.** Files written to a fixed directory, a Redis database used outside the cache, a search index or a port on the machine are shared by every worker unless you segment them with the process token. 4. **Runner options.** Some Pest or PHPUnit options are not available in parallel mode; the docs name `--do-not-cache-result`. ## Reading the numbers after the switch Parallel runs rarely scale linearly with cores. Each process boots its own applications, the first run pays for creating and migrating every per-process database, and a handful of very slow tests can leave one worker running long after the others have finished. So compare wall-clock times over several runs, not the first one, and keep `--profile` in the loop: a single test that takes two minutes caps the whole parallel run at two minutes, whatever `--processes` says. ## A realistic plan For the twenty-minute suite: 1. Run `--profile`, fix the slowest causes, and move pure-logic tests off the application boot. 2. Add paratest and run `--parallel` locally; fix the order-dependent and shared-resource failures it reveals. 3. Give the CI database user create rights, pick a `--processes` value that matches the runner's cores, and keep `--recreate-databases` for runs after migrations change. 4. Re-profile periodically, because the slowest ten drift as the app grows. Interviewers asking this want the order of operations, **measure, fix, then parallelize**, and the specific per-process isolation Laravel performs, not a generic "run tests on more machines" answer.

  • Why do Laravel's parallel test databases survive between runs, and when do you rebuild them?
    Creating and migrating a database per process is slow, so Laravel keeps `boxes_test_1` and the others and reuses them on the next run. When their schema no longer matches the migrations, or they hold leftover state, run `php artisan test --parallel --recreate-databases` to drop and recreate them.
  • Parallel runs pass locally but fail in CI with an access-denied error before any test runs; what do you check?
    The CI database user's privileges. Laravel creates a database per process, named after the original database plus `_test_` and the token, so the user needs permission to create and drop databases. Either grant that on the test server or point tests at in-memory SQLite, which needs no creation.
  • Which resources do you still have to separate yourself in a Laravel parallel run?
    Anything beyond the database, cache prefix and compiled-view path: files in a fixed storage directory, a Redis database used directly, a search index, or a local port. Register a `ParallelTesting` hook and use the process token to give each worker its own name or path.

Parallel testing is like opening more checkout lanes in a supermarket: each lane needs its own till, which Laravel provides as a database, cache prefix and view folder, but the lanes still share the stockroom, so anything outside those tills must be divided up by hand.

saying these in an interview costs you the question

  • Parallel runs share one database, kept safe by each test's transaction.
  • --parallel works without installing brianium/paratest.
  • Laravel isolates every external resource per process automatically.
  • Parallelizing first is better than profiling; more cores fix any slow suite.
  • Per-process test databases are dropped after every run by default.