<? phpukraine СПІВБЕСІДИ
⌕Пошук по платформі
ТЕСТУВАННЯ · SENIOR

Як тримати великий набір тестів швидким і надійним?

Спершу виміряти (`pest --profile`, час по джобах CI), потім прибрати дешеві втрати: хешування паролів, міграції на кожен тест, справжні `sleep()` і мережу, зайві дані з фабрик. Далі паралелити (`--parallel`, шардування на кілька машин), тримаючи тести ізольованими: своя база на процес, без спільних файлів і статики. Надійність тримається на правилі «flaky-тест це баг з власником», а не на автоматичних повторах.

Набір іде 25 хвилин, розробники перестали запускати його локально. Що робитимете?
Ви ввімкнули `--parallel`, і тести почали падати випадково. Чому?
Як ви боретеся з flaky-тестами в команді з двадцяти людей?
Що б ви поміняли в CI, щоб pull request перевірявся за пʼять хвилин?
швидкість тестів parallel шардування CI flaky-тести ізоляція

Найбільша шкода від повільного набору в тому, як змінюється поведінка команди: тести перестають запускати локально, pull request чекають CI пів години, а червоний прогін перезапускають, не читаючи. Тому на співбесіді Senior-рівня чекають порядку дій, і починається він з вимірювання. vendor/bin/pest --profile показує найповільніші тести, а час по джобах CI показує, де губляться хвилини поза тестами: установка залежностей, міграції, збирання фронтенду.

Найчастіше половину часу дає невелика частина тестів, і причини в них банальні. Кожен User::factory() хешує пароль, тож BCRYPT_ROUNDS=4 у phpunit.xml економить десятки мілісекунд на користувача. DatabaseMigrations проганяє міграції перед кожним тестом, тоді як RefreshDatabase мігрує один раз і відкочує транзакції, а schema:dump замінює сотні міграцій одним дампом. Паузи в коді ретраїв пишуть через Sleep::for() замість sleep(), і тоді Sleep::fake() їх гасить. Мережу відрізають Http::fake() і Http::preventStrayRequests(). Фабрики, що через has()->count(50) створюють сотні рядків заради одного асерту, переписують на мінімальні дані.

Друга частина роботи структурна. Варіації правил переносять з feature-тестів у швидкі unit-тести домену, на рівні HTTP лишають сценарії, браузерні тести тримають для критичних шляхів. Найдорожчі перевірки, як повне мутаційне тестування чи довгий e2e, ідуть за розкладом уночі, а на pull request лише змінене.

Лише після цього вмикають паралельність: --parallel у Pest (на ParaTest) усередині машини і --shard між джобами CI. Умова в неї одна: ізоляція. Laravel створює для кожного процесу окрему тестову базу, але файли в storage, ключі в Redis, порти й статичні властивості лишаються спільними, і для них є хуки ParallelTesting::setUpProcess() і setUpTestCase(). Тести, що залежали від порядку чи від даних сусіда, після ввімкнення паралельності почнуть падати: проблема була й раніше, паралельність лише зробила її видимою.

Надійність тримається на політиці. Flaky-тест це баг з власником і задачею: його відтворюють (->repeat(), --order-by=random з seed, --parallel), знаходять причину і виправляють. Автоматичний повтор у CI робить набір зеленим, але прибирає єдиний сигнал про гонки, які потім проявляться на проді. Набір цінний рівно доти, доки червоний прогін означає справжню проблему.

// tests/Pest.php: Laravel і база лише для Feature, Unit іде без фреймворку.
pest()->extend(Tests\TestCase::class)
    ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
    ->in('Feature');

// tests/TestCase.php: дешеві налаштування, що знімають хвилини з прогону.
abstract class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        Http::preventStrayRequests();   // справжня мережа в тестах = падіння, а не 5 с таймауту
        Sleep::fake();                  // Sleep::for(3)->seconds() не чекає по-справжньому
    }
}

// AppServiceProvider::boot(): окрема тека файлів для кожного паралельного процесу.
ParallelTesting::setUpTestCase(function (int $token) {
    config(['filesystems.disks.local.root' => storage_path("framework/testing/disk-{$token}")]);
});

// phpunit.xml: <env name="BCRYPT_ROUNDS" value="4"/>, CACHE_STORE=array, QUEUE_CONNECTION=sync

// Локально:  vendor/bin/pest --profile          найповільніші тести
//            vendor/bin/pest --parallel
// CI-матриця: vendor/bin/pest --parallel --shard=1/4   (2/4, 3/4, 4/4 в інших джобах)
Підхід від вимірювання: профіль найповільніших тестів і джоб CI, а не «купимо більші раннери».
Конкретні дешеві виправлення в Laravel: низький `BCRYPT_ROUNDS` у `phpunit.xml`, `RefreshDatabase` з транзакціями замість міграцій на кожен тест, `schema:dump` для швидкого старту, `Sleep::fake()`, `Http::preventStrayRequests()`, мінімум рядків з фабрик.
Паралельність і її умова: ізоляція. Laravel створює окрему тестову базу для кожного процесу, але файли, кеш, порти й статичний стан лишаються спільними.
Структура набору: більше логіки в швидких unit-тестах, feature на сценарії, браузерні лише на критичні шляхи; повільні перевірки (мутації, повний e2e) на ніч.
Політика flaky: тест, що падає випадково, отримує власника й виправляється або видаляється з поясненням, а не лікується повтором у CI.
Почати з паралельності, не знаючи, що повільне: двадцять тестів по 10 секунд з `sleep()` або реальними HTTP-запитами так і лишаться найдовшими.
Вмикати автоматичні повтори впалих тестів у CI або масово ставити `->flaky()` (у Pest 5 він дає тесту до трьох спроб): flaky-тести перестають бути видимими, а гонки в коді доходять до проду.
Переносити feature-тести на SQLite `:memory:` заради швидкості, коли прод на PostgreSQL чи MySQL: різні типи, JSON-функції й блокування дають зелені тести на зламаному коді.
Ставити `DatabaseMigrations` (міграції перед кожним тестом) там, де вистачає транзакцій `RefreshDatabase`.
Ділити набір на шарди вручну за теками: один шард іде 12 хвилин, інший дві.
ПОРАДА

Відповідайте як план з цифрами: «спершу `--profile` і час джоб; зазвичай 10% тестів дають половину часу. Потім дешеві налаштування середовища, потім `--parallel` з перевіркою ізоляції, і шардування в CI. Окремо правило: flaky-тест це баг з власником». План з порядком дій звучить сильніше за перелік прапорців.

оновлено 8 жовтня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Без профілю легко паралелити тести, які повільні через sleep() чи мережу. Laravel створює окрему базу на кожен процес, але файли, кеш і статичний стан лишаються спільними, і тести, що від них залежать, починають падати. Повтори ховають flaky-тести, а SQLite замість бойової бази поводиться інакше з типами, JSON і блокуваннями.