Senior: питання на співбесіді з теми «Тестування»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
3 питання
Знімкові тести (snapshot) зберігають результат при першому запуску у файл, а при наступних - порівнюють з ним.
it('серіалізує замовлення для API', () => {
expect(toApiPayload(order)).toMatchSnapshot();
});
it('форматує адресу', () => {
expect(formatAddress(address)).toMatchInlineSnapshot(`"м. Київ, вул. Хрещатик, 1"`);
});
Оновлення знімків після навмисної зміни: npx vitest -u.
Коли знімки корисні:
- великі структури даних, які складно перевірити вручну: payload для API, згенерована конфігурація, результат парсера;
- виявлення неочікуваних змін у серіалізації: додали поле - знімок це покаже;
- inline-знімки для коротких значень - видно прямо в тесті, рев'ю бачить очікування.
Коли шкодять:
- знімки всієї розмітки компонента змінюються від кожної правки верстки чи класу. Розробник звикає запускати
-uне дивлячись - і знімок перестає щось перевіряти; - великі знімки ніхто не читає на рев'ю: diff на 300 рядків приймається «на віру»;
- нестабільні дані (дати, випадкові id) змушують фіксувати чи маскувати значення, інакше тест падає щоразу;
- знімок замість думки: тест не каже, що важливо в результаті. Явна перевірка
expect(payload.total).toBe(1999)документує намір, знімок - ні.
Візуальна регресія порівнює знімки екрана:
await expect(page).toHaveScreenshot('checkout.png', {
maxDiffPixelRatio: 0.01,
mask: [page.getByTestId('current-date')],
});
Ловить те, що інші тести не бачать: зламаний CSS, зсунуту верстку, зниклі іконки, проблеми темної теми.
Складнощі візуальних тестів:
- рендеринг шрифтів і згладжування відрізняються між ОС і навіть версіями браузера - знімки, зроблені на Mac, не збігаються з Linux у CI. Еталонні знімки генерують у тому самому середовищі (Docker-образ Playwright), де запускаються тести;
- динамічний вміст (дати, реклама, аватари, анімації) - маскувати й вимикати;
- поріг розбіжності - надто суворий дає хибні падіння, надто м'який пропускає реальні зміни;
- зберігання еталонів у репозиторії збільшує його розмір.
Практичний підхід: небагато візуальних тестів для ключових сторінок і компонентів дизайн-системи, явні перевірки поведінки - для логіки, знімки даних - для великих структур з обов'язковим переглядом diff на рев'ю.
У Laravel-проєкті фронтенд (Blade з Alpine, Livewire, Inertia з Vue чи React) тісно пов'язаний з бекендом. Браузерний тест має перевіряти обидві частини разом - і питання в тому, з якого боку його писати.
Pest browser tests (плагін pestphp/pest-plugin-browser, працює на Playwright):
it('користувач підписується на розсилку', function () {
$page = visit('/');
$page->fill('email', 'olena@example.com')
->click('Підписатися')
->assertSee('Дякуємо за підписку')
->assertNoJavaScriptErrors();
expect(Subscriber::where('email', 'olena@example.com')->exists())->toBeTrue();
});
it('сторінки відкриваються без помилок', function () {
visit(['/', '/blog', '/jobs'])->assertNoSmoke();
});
- той самий процес і база, що в PHP-тестах: фабрики,
RefreshDatabase,actingAs(),Mail::fake()працюють як у звичайних feature-тестах; - перевірка бази поруч з перевіркою інтерфейсу - без API для підготовки даних;
- smoke-тести (
assertNoSmoke,assertNoJavaScriptErrors) дешево ловлять зламані сторінки й помилки в консолі; inDarkMode(), мобільні пристрої, знімки екрана для візуальних порівнянь.
Playwright напряму (тести на JavaScript/TypeScript):
- окремий застосунок запущений як справжній сервер (
webServerу конфігурації), тести бачать його лише через HTTP; - підготовка даних - через сидери, спеціальні тестові ендпойнти чи API, що ускладнює ізоляцію;
- повний API Playwright: перехоплення мережі, кілька вкладок і контекстів, трасування, компонентні тести;
- природно для команди, що пише фронтенд на TypeScript, і для SPA, де бекенд - окремий сервіс.
Як обрати:
| Ситуація | Що зручніше |
|---|---|
| Blade, Livewire, Inertia в одному репозиторії з Laravel | Pest browser tests |
| команда пише переважно на PHP | Pest |
| окремий SPA-фронтенд, бекенд лише API | Playwright |
| складна клієнтська логіка: офлайн, кілька вкладок, мережеві умови | Playwright |
У CI:
- браузери треба встановити (
npx playwright install --with-deps chromium) і закешувати; - ассети зібрати (
npm run build), інакше сторінки без JavaScript і CSS; - паралельний запуск і шардування для довгих наборів;
- знімки екрана й траси як артефакти збирання - щоб розбиратися з падіннями без локального відтворення.
Розподіл рівнів: браузерні тести - для критичних сценаріїв і smoke-перевірок сторінок, Livewire- і feature-тести - для логіки компонентів і контролерів, Vitest - для складної клієнтської логіки окремо від браузера.
На старті будь-які тести швидкі. Через рік набір з тисячі тестів може йти 15 хвилин, падати випадково й гальмувати кожен pull request. Структура з самого початку визначає, чи станеться це.
1. Правильний рівень для кожної перевірки:
- логіка (розрахунки, перетворення, валідація) - винесена в чисті функції й перевіряється unit-тестами в середовищі
node, без DOM; - поведінка компонентів - інтеграційні тести з Testing Library;
- критичні сценарії - кілька E2E.
Найпоширеніша причина повільного набору - перевірка логіки через E2E: кожен варіант знижки перевіряється відкриттям браузера.
2. Середовище за потребою: jsdom у всіх файлах сповільнює й ті тести, яким DOM не потрібен. Середовище задають для конкретних файлів чи проєктів:
// vitest.config.js
export default defineConfig({
test: {
projects: [
{ test: { name: 'unit', environment: 'node', include: ['**/*.unit.test.js'] } },
{ test: { name: 'dom', environment: 'happy-dom', include: ['**/*.dom.test.js'] } },
],
},
});
3. Ізоляція й детермінованість:
- кожен тест готує свої дані й не залежить від порядку запуску;
- моки й глобальні заглушки відновлюються після кожного тесту (
restoreMocks,unstubGlobalsу конфігурації); - час, випадкові значення й мережа - під контролем тесту (фейкові таймери,
vi.setSystemTime, MSW).
4. Швидкий зворотний зв'язок:
vitest --changed- лише тести, пов'язані зі зміненими файлами;- у CI - паралельний запуск і шардування:
vitest run --shard=1/4; - E2E - окремою задачею CI, що не блокує швидкі перевірки.
5. Боротьба з нестабільними тестами: тест, що падає випадково, швидко вчить команду ігнорувати червоний CI. Нестабільний тест або виправляють одразу, або тимчасово виключають (з задачею на виправлення), але не перезапускають мовчки.
6. Тести як код:
- спільні фабрики тестових даних замість копіювання об'єктів у кожному тесті;
- допоміжні функції для монтування з провайдерами;
- назви тестів описують поведінку: «показує помилку для порожньої пошти», а не «test 3».
7. Що прибирати: тести, що перевіряють реалізацію (внутрішні виклики, точну розмітку), знімки, які оновлюють не дивлячись, і дублікати тієї самої перевірки на різних рівнях.
Метрики здоров'я набору: час прогону в CI, частка нестабільних тестів, кількість тестів, змінених при рефакторингу без зміни поведінки. Остання - найкращий індикатор того, що тести перевіряють деталі реалізації.