Тест-дані: фабрики, фікстури, ізоляція
Зміст
Автотест — це не лише кроки й перевірки. Перш ніж клікнути «Оплатити», хтось має створити користувача, товар і кошик із грошима на балансі. Це «щось» і є (test data), а стан (state), у який ви приводите застосунок перед перевіркою, — половина тесту. Коли дані брудні, спільні або непередбачувані, тест червоніє там, де продукт справний, або зеленіє там, де є . Дані — найтихіший руйнівник стабільності : їх не видно в кроках, але саме через них тест «іноді» падає.
Це канонічна глава про стратегію тест-даних — інші глави (, паралелізація, тести API і БД) посилаються сюди. Тема має бейдж «поглиблення»: при першому проході автоматизації її можна відкласти й повернутися, коли ваші тести почнуть заважати одне одному або червоніти без змін у продукті. Але вміння тримати дані під контролем відрізняє того, хто пише тести, від того, хто пише стабільні тести.
Фікстури, стан і передумови: словник
Три слова, які плутають. (fixture) — це фіксоване, відоме оточення, у якому виконується тест: набір даних, залогінений користувач, піднятий сервіс. Історично фікстура — це саме файл із заготовленими даними; у сучасних (Playwright, pytest) фікстурою називають ще й механізм, що готує це оточення й прибирає його по завершенні. Стан (state) — конкретна ситуація в системі на момент перевірки: «є замовлення в статусі paid». (precondition) — те, що має бути істинним ще до першого кроку тесту.
Ключова думка, до якої ми повертатимемося: arrange-фаза автотесту (див. Анатомію автотесту) — це і є підготовка даних та стану. Питання не «чи готувати», а «звідки брати» і «як не дати їм протекти між тестами».
Статичні фікстури проти генерації
Найпростіший спосіб мати дані — покласти їх у файл: users.json, products.json, дамп у репозиторії. Тест читає файл, підставляє значення. Це статична фікстура.
Її сила — детермінованість і читабельність: у файлі видно рівно ті значення, на яких тест зелений. Її слабкість — старіння й колізії. Захардкоджений user@example.com працює, поки тест один. Щойно два тести (або два ) створюють користувача з тим самим email — колізія унікального ключа, і другий падає з 409 Conflict. Статичний дамп ще й тихо протухає: сьогодні в ньому активна підписка, а через місяць бекенд змінив правила, і «активна» перетворилась на «прострочена».
Протилежний підхід — генерувати дані на льоту: щоразу свіжий email, випадкове імʼя, унікальний ідентифікатор. Це прибирає колізії й старіння, але коштує детермінованості: коли значення різні щопрогону, важче зрозуміти, на яких саме даних тест упав, і легше проґавити edge-кейс, якого генератор випадково не дав.
| Критерій | Статична фікстура | Генерація |
|---|---|---|
| Детермінованість | Висока (видно значення) | Нижча (значення різні) |
| Колізії під паралель | Легко: спільний ключ | Малоймовірні |
| Старіння даних | Тихо протухає | Завжди свіжі |
| Edge-кейси | Можна закласти явно | Треба додавати руками |
| Читабельність тесту | Висока | Нижча |
Правильна відповідь — не «або-або», а розподіл ролей. Довідкові, стабільні дані (список країн, тарифні плани, конфігурація фіч) — статичні: вони й мають бути фіксованими. , унікальні сутності (користувачі, замовлення, документи) — генеровані, бо кожен тест потребує свіжий екземпляр. А небезпечну класику (порожній рядок, пробіли, 4-байтний unicode, гранична довжина) генератор випадково не видасть — її закладають явно, як окремі кейси (детальніше — у розділі про тест-дизайн).
Фабрики і faker
Коли даних треба багато й вони схожі, зʼявляється фабрика (factory) — функція, що повертає готовий обʼєкт із розумними дефолтами, дозволяючи перевизначити лише те, що важливо для конкретної перевірки. Це прямий родич патерна Builder і (див. патерни в автоматизації). Другий термін варто знати точно, бо він старий і живучий: «An object mother is a kind of class used in testing to help create example objects that you use for testing», а автор запису прямо знімає з нього ореол — це «just a catchy name for such a factory», де «such a factory» означає фабрику стандартних наборів даних. Назва зʼявилася на проєкті Thoughtworks на межі століть. Побічна вигода, про яку рідко думають: готові обʼєкти «become familiar to the team, often invading even discussions with the users» — тобто «Оля з безкоштовною підпискою» стає спільною мовою команди, а не лише рядком у фікстурі.
type User = { email: string; name: string; role: 'admin' | 'member'; balance: number };
function buildUser(overrides: Partial<User> = {}): User {
return {
email: faker.internet.email(),
name: faker.person.fullName(),
role: 'member',
balance: 0,
...overrides,
};
}
// у тесті беремо дефолт і міняємо ЛИШЕ те, що впливає на перевірку
const admin = buildUser({ role: 'admin', balance: 1000 });
Чому це важливо, а не просто «менше коду»: фабрика робить тест читабельним за наміром. У виклику buildUser({ role: 'admin' }) видно, що для цієї перевірки суттєва тільки роль, а email та імʼя — шум. Без фабрики кожен тест тягне повний обʼєкт із десятьма полями, і читач не розуміє, які з них навмисні, а які випадкові.
Значення для полів дає faker — бібліотека, що генерує реалістичні дані: імена, адреси, email, телефони, UUID. Реалістичність корисна: ідеальне test test бреше про те, як виглядатимуть справжні дані, а довге прізвище з апострофом чи нелатинські літери ловлять баги верстки й кодувань.
Одна пастка faker: випадковість не гарантує унікальності. faker.internet.email() теоретично може двічі видати той самий рядок, а на великій сюїті — обовʼязково видасть. Тому для ключів, що мусять бути унікальними, домішують гарантовано унікальний компонент (мітку часу, індекс воркера, UUID), а не покладаються на «навряд чи збіжиться».
Зворотний бік випадковості — недетермінованість. Коли треба відтворити фейл, генератору задають :
import { faker } from '@faker-js/faker';
faker.seed(123); // однаковий сид -> однакова послідовність значень
Зерно перетворює «випадкове, але відтворюване» на кероване: у CI можна логувати зерно і, отримавши червоний тест, повторити рівно ту саму послідовність даних локально. Це компроміс: детермінованість фікстури плюс різноманіття генерації.
Як довести систему до потрібного стану
Припустимо, тесту потрібне замовлення в статусі paid. Створити його можна чотирма шляхами, і вибір між ними — одне з найважливіших рішень у дизайні сюїти.
Через UI. Тест сам клікає по формах, щоб створити передумову. Єдиний випадок, коли це виправдано, — коли сам процес створення і є обʼєктом перевірки. У всіх інших випадках готувати стан кліками — марнотратство: повільно, крихко (кожна зміна форми ламає чужі тести) і змішує підготовку з перевіркою.
Через API. Той самий стан створюється HTTP-запитом до публічного API застосунку. Це золотий стандарт arrange-фази: на порядок швидше за UI, стабільніше (API міняється рідше за верстку) і, головне, проходить через ту саму бізнес-логіку, що й реальний користувач, — тож не створює станів, неможливих у продакшені. Логін теж роблять через API один раз на ран і перевикористовують стан сесії (storageState) — механіка описана у главі про автентифікацію та авторизацію.
Пряма БД. INSERT прямо в таблицю. Найшвидший шлях і єдиний, коли стан узагалі недосяжний через API (наприклад, треба «зістарити» запис на 40 днів назад). Ціна висока: ви обходите всі інваріанти застосунку — валідацію, тригери, обчислювані поля — і можете створити стан, який реальний код ніколи не породив би. Такий тест зелений на неможливих даних. Плюс жорстке звʼязування зі схемою БД: міграція таблиці ламає підготовку. Пряму БД тримають як виняток, не як звичку ( через БД проти API — детальніше в розділі про бази даних).
Бекдор (backdoor). Спеціальний тестовий , який існує лише в тестових середовищах: POST /test/seed-order. Компроміс між API і БД — швидко й без клікання, але через контрольований код, а не сирий INSERT. Головна засторога: у проді бекдор не повинен бути доступним. Тестовий ендпоінт, що доїхав до продакшену, — це готова (обхід автентифікації, створення адмінів). Тому його або вирізають із прод-збірки, або вмикають лише за прапорцем середовища.
Правило: готуй стан на найнижчому достатньому рівні. UI — тільки коли перевіряєш сам UI-флоу; API — за замовчуванням; БД і бекдор — коли інакше стан недосяжний, і свідомо, з розумінням, що ти обходиш логіку продукту.
Ізоляція: на тест і на воркер
Ізоляція — це гарантія, що дані одного тесту не бачить і не псує інший. Без неї сюїта детермінована рівно доти, доки виконується послідовно й в одному порядку; варто ввімкнути паралелізацію — і вона розсипається.
Є два рівні, які плутають.
Ізоляція на тест. Кожен тест створює власні дані в arrange і не залежить від того, що лишив попередній. Це прямий наслідок незалежності тестів: якщо тест B читає користувача, якого створив тест A, то B впаде, щойно ви запустите його окремо, зміните порядок або зашардуєте сюїту між машинами. Симптом такого прихованого звʼязування — «тест проходить у повній сюїті, але падає, коли ганяєш його самого».
Ізоляція на воркер. Коли тести йдуть паралельно в N процесах-воркерах, їхні дані не мають колідувати між процесами. Два воркери, що реєструють user@test.local, — це 409 на другому. Розвʼязань два: або унікалізувати дані ключем воркера, або дати кожному воркеру власний набір (акаунт, tenant, простір імен).
// унікальність ключем воркера й міткою часу
const email = `user+${test.info().workerIndex}-${Date.now()}@test.local`;
// один акаунт на воркер: створюється раз, живе весь прогін воркера
export const test = base.extend<{}, { account: Account }>({
account: [async ({}, use, workerInfo) => {
const account = await createAccount(`w${workerInfo.workerIndex}@test.local`);
await use(account); // усі тести цього воркера працюють у ньому
await deleteAccount(account.id); // прибирання по завершенні воркера
}, { scope: 'worker' }],
});
Воркер- — компроміс між швидкістю (не створювати акаунт на кожен тест) і ізоляцією (воркери не перетинаються). Тонкий момент: якщо кілька тестів в одному воркері ділять акаунт, вони знову звʼязані в межах воркера — тому дані мутувати мають так, щоб не заважати одне одному (різні замовлення в одного юзера, а не один спільний кошик).
Прибирання: after-хуки, TTL і одноразові акаунти
Створили дані — маєте прибрати. Інакше тестова база пухне, унікальні ключі вичерпуються, а наступні прогони спотикаються об чужі залишки.
Базовий інструмент — teardown у хуках (afterEach, afterAll) або у фікстурі після use. Але наївне «afterEach усе підмете» має дірки:
- Тест упав посередині arrange — половина даних створена, а хук чистить за списком, якого немає.
- Сам teardown кинув помилку — і решту не прибрало.
- Воркер помер (, OOM) — хуки взагалі не виконались.
Тому надійна стратегія двошарова: after-хук як основний прибиральник плюс страховка, що не залежить від успіху тесту. Страховкою буває:
- TTL (time to live) — тестові сутності живуть обмежений час, а фоновий (або сам застосунок) видаляє прострочене. Осиротілі дані самі зникають.
- Нічне — окремий скрипт за розкладом, що зносить усе тестове старше за N годин за домовленою ознакою (префікс email
test+, окремий tenant). - Одноразові акаунти — замість чистити стан, щоразу створюють свіжий акаунт і не переймаються його вмістом; прибирає їх той самий TTL-джоб.
- Скидання стану замість вибіркового видалення — де можливо, повне пересоздання схеми/ між прогонами (у БД-тестах — відкат транзакції; механіка — у розділі про бази даних).
Важливий нюанс залежно від ранера: у Playwright afterEach і teardown фікстури виконуються навіть після падіння тесту — це рятує від першої дірки. Але від смерті воркера чи помилки в самому teardown не рятує ніщо на рівні хуків, тому зовнішня страховка (TTL, нічне прибирання) потрібна завжди, коли тести пишуть у спільне сховище.
Спільні оточення
Ідеальний світ — ізольована база на кожен прогін (докеризована, testcontainers). Реальність частіше така: один , спільний на кілька команд, автотести, ручних тестувальників і демо для замовника. Це джерело болю, яке не лікується кодом тесту.
Що ламається на спільному стенді: чужі дані (хтось залив 200 замовлень, і ваш «перевір, що список порожній» червоніє), гонки за спільний ресурс (два прогони деруться за єдиний тестовий акаунт — черга й ), руйнування стану під ногами (ручний тестувальник видалив користувача, на якому тримався ваш сценарій), дрейф конфігурації (на стенді ввімкнули , і поведінка змінилась).
Пом'якшення — все та ж ізоляція, тільки на рівні даних, а не інфраструктури: не покладайтеся на глобальний стан. Тест не має вважати, що список порожній, — він створює свій маркований запис і шукає саме його. Не «є рівно 3 користувачі», а «серед користувачів є той, кого я щойно створив», з унікальним префіксом, щоб фільтрувати своє серед чужого. По суті, тест на спільному стенді має бути написаний так, ніби він там не один, — бо він там не один.
Прод-дані: чому ні
Спокуса зрозуміла: «візьмімо дамп продакшену — і дані будуть реалістичні». Це погана ідея з кількох незалежних причин, кожної з яких достатньо.
Юридичний і приватний . Дамп прода містить персональні дані (personally identifiable information, PII) реальних людей: імена, email, телефони, адреси, іноді платіжки. Копія в тестовій базі, в артефактах CI, у логах, на ноутбуці інженера — це обробка персональних даних поза призначенням і потенційний витік. GDPR (Регламент ЄС 2016/679) вимагає мінімізації даних і законної підстави; тестування продакшн-дампом такої підстави зазвичай не має.
Непередбачуваність і старіння. Прод живий: користувача, на якому тримався тест, видалили; підписку скасували; баланс змінився платіжкою вночі. Тест на таких даних недетермінований за побудовою — він залежить від стану, який ви не контролюєте.
Ризик мутації. Найгірший сценарій — тест не на копії, а на самому проді: автоматизація, що «перевіряє видалення», зносить дані справжнього клієнта. Прод — не тестовий полігон для сценаріїв, що змінюють стан.
Що робити натомість: потрібне «як прод», а не «прод». Реалістичну структуру дають синтетичні дані з faker і фабрик або анонімізований (деперсоналізований) зріз — коли з прод-дампу PII замінюють на фейкові з збереженням форми й обсягу (маскування). Тоді ви маєте реалістичний розподіл і без чужих персональних даних. І окремо: якщо тестуєте саме на проді свідомо (post-deploy smoke), він має бути read-only або працювати на спеціально виділених синтетичних акаунтах, а не на живих користувачах.
Типові помилки
- Виглядає як -тест, який «іноді падає в CI, а локально зелений», а насправді два воркери деруться за той самий email/акаунт — колізія унікального ключа під паралеллю. Лікується ізоляцією на воркер, а не ретраями.
- Виглядає як стабільний зелений тест, а насправді він asserts на дані, які створив попередній тест у сюїті. Запустіть його окремо або зашардуйте — і він упаде. Приховане звʼязування через порядок.
- Виглядає як «afterEach усе прибере», а насправді тест упав до кінця arrange або teardown сам кинув помилку — база тече. Потрібна TTL-страховка поверх хуків.
- Виглядає як унікальний email від
faker.internet.email(), а насправді випадковість не гарантує унікальності — на великій сюїті збіг настане. Унікальність домішують явно (індекс воркера, UUID, час). - Виглядає як «реалістичні дані з прод-дампу», а насправді PII в репозиторії/логах, юридичний ризик і половина записів протухла. Треба синтетика або анонімізований зріз.
- Виглядає як швидка й зручна підготовка через прямий
INSERT, а насправді тест зелений на стані, який застосунок ніколи не породив би, бо ви обійшли валідацію й тригери.
Підсумок
- Кожен тест володіє своїми даними. Arrange створює те, що перевіряє; тест не залежить від порядку, іншого тесту чи глобального стану стенду.
- Готуй стан на найнижчому достатньому рівні. UI — лише коли перевіряєш сам UI-флоу; API — за замовчуванням; БД/бекдор — тільки коли стан інакше недосяжний, і свідомо.
- Дефолти — з фабрики, унікальність — з генератора. У тест виноси лише поле, що впливає на перевірку; закладай явно, бо faker їх не дасть.
- Ізоляція під паралель + страховка на прибирання. Дані не колідують між воркерами (унікальний ключ або акаунт на воркер); after-хуки чистять, а TTL і нічне прибирання підмітають .
- Прод-дані в тестах — ні. PII, юридичний ризик, непередбачуваність і мутація живих даних. Потрібне «як прод» (синтетика, анонімізований зріз), а не «прод».
Можливі питання
- «Звідки ваші тести беруть дані?» Інтервʼюер перевіряє, чи ви взагалі думали про стратегію, а не хардкодите email у кожному тесті. Сильна відповідь розрізняє довідкові (статичні) й транзакційні (генеровані) дані й пояснює, чому.
- «Тест створює замовлення для перевірки чекауту. Як приготуєте його?» Класика на розуміння сходів UI/API/БД. Очікують «через API, не кліками» з поясненням чому (швидкість, стабільність, поважання інваріантів) і коли виправданий виняток (перевіряємо сам флоу створення).
- «Тест зелений локально, червоний у CI — з боку даних, що підозрюєте?» Дивляться на розуміння паралелізму: колізії між воркерами, залежність від порядку, спільний стенд, невідтворюваний faker без зерна.
- «Як прибираєте дані після тестів?» Слабка відповідь — «afterEach». Сильна — двошарова: хук плюс TTL чи нічне прибирання, бо хук не виконається при смерті воркера чи падінні в arrange.
- «Можна тестувати на копії продакшену?» Тут перевіряють зрілість: PII і GDPR, старіння даних, ризик мутації; альтернатива — синтетика або анонімізований зріз. Не побачити приватного ризику — тривожний сигнал.
- «Фабрика проти статичної фікстури — коли що?» Дивляться, чи розумієте компроміс детермінованість/старіння/колізії, а не завчили одну відповідь.
Джерела
Фікстури, стан і передумови: словник
- ISTQB® CTAL-TAE Syllabus v2.0 — §4.2: фікстура задає стан тестового оточення перед тестом, дає контроль над даними й прямо повʼязана з повторюваністю та атомарністю.
- ISTQB® CTFL Syllabus v4.0.1 — §1.4.3–1.4.4: підготовка даних і середовища — планована активність ; test implementation включає створення тестового приладдя, зокрема тестових даних.
Статичні фікстури проти генерації
- xUnit Test Patterns — Erratic Test — де проходить межа: генерація сама по собі не шкідлива (унікальні ключі для БД названо легітимним випадком), шкідливо, коли згенероване значення йде на вхід алгоритму, поведінка якого від значення залежить.
- ISTQB® CTAL-TAE Syllabus v2.0 — data-driven тестування будується поверх структурного скриптингу, дає дешеве розширення через дані, а ціна — потреба в управлінні тестовими даними.
- Faker (@faker-js/faker) — Usage — генератор за замовчуванням дає різні значення щоразу, а зерно задають для сталих результатів; там же дві межі: після оновлення бібліотеки те саме зерно може дати інші значення, а методи з відносними датами відтворюваними не стають.
- Martin Fowler — ObjectMother — фабрика стандартних фікстур під тестовою назвою: «Object Mother is just a catchy name for such a factory», а сам набір даних джерело називає
test fixture. - ISTQB® CTAL-TAE Syllabus v2.0 — чому це не косметика: хардкоду канон радить уникати саме через перехід на дані з єдиного джерела.
Як довести систему до потрібного стану
- Playwright — API testing — підготовка серверного стану перед відкриттям застосунку названа офіційно підтримуваним застосуванням API-контексту, а стан автентифікації переноситься через
storageState(). - ISTQB® CTFL Syllabus v4.0.1 — §4.1: конкретні значення виводяться з технік — техніки допомагають визначити умови, й тестові дані.
- xUnit Test Patterns — Fresh Fixture — кожен тест будує власну новісіньку фікстуру для приватного вжитку; порядок відступу заданий джерелом: свіжа → незмінна спільна → і лише потім звичайна спільна.
- Playwright — Test fixtures — механіка : тестова фікстура прибирається після кожного тесту, воркерна — коли завершується воркер, і воркер перевикористовується далі, лише поки воркерні фікстури збігаються.
- Playwright — Test Isolation (browser contexts) — три названі наслідки ізоляції: немає перенесення падіння, легше дебажити, не треба думати про порядок у паралелі та .
Прибирання: after-хуки, TTL і одноразові акаунти
- Playwright — Test Isolation (browser contexts) — чому стратегія «прибирати між тестами» програє стратегії «починати з нуля»: про прибирання легко забути, а дещо прибрати неможливо.
- xUnit Test Patterns — Erratic Test — персистентна спільна фікстура названа передумовою «неповторюваного тесту» (пройшов один раз, далі падає), і лікування — свіжа фікстура на кожен тест.
- Playwright — Test fixtures — де живе прибирання у фікстурі: усе після
useвиконує ранер, і воркерна фікстура має власний таймаут.
- xUnit Test Patterns — Erratic Test — «війна прогонів» можлива тільки за глобально спільної фікстури, до якої різні тести звертаються й іноді її змінюють — це і є діагноз спільного стенду.
- xUnit Test Patterns — Fresh Fixture — і тонкість, яку на спільних оточеннях найчастіше пропускають: приватна БД свіжою фікстурою не є, бо наступний прогін дістане той самий стан.
- Регламент (ЄС) 2016/679 (GDPR) — ст. 5: заборона тримається не на одному принципі мінімізації, а на сукупності — обмеження метою, мінімізація, обмеження зберігання, цілісність і конфіденційність, підзвітність; модальність
shall, тобто вимоги, а не поради. - Регламент (ЄС) 2016/679 (GDPR) — і межа, яку найчастіше плутають: псевдонімізовані дані лишаються персональними, а принципи захисту не застосовуються лише до анонімних; різниця у зворотності.
- Faker (@faker-js/faker) — Usage — інструментальний бік заміни: синтетичні значення потрібної форми, з керованим зерном для відтворюваності.
Відео до теми
Що таке тестові дані і чому їх називають прихованим джерелом флаку?
(test data) — це все, що має існувати в системі до того, як тест почне клікати чи слати запити: користувач, товар, баланс, замовлення в потрібному статусі. Разом із ними йде поняття стану (state) — конкретна ситуація застосунку на момент перевірки. Небезпека в тому, що дані не видно в кроках сценарію: у звіті ви бачите «клік на кнопку», а не «взяли протухлий акаунт із чужого прогону». Через це тест червоніє там, де продукт справний, або, навпаки, зеленіє над реальною . Саме тому стратегія даних відрізняє того, хто просто пише тести, від того, хто пише стабільні тести.
Чим фікстура відрізняється від стану і передумови?
Це три різні речі, які часто змішують. (fixture) — фіксоване відоме оточення для тесту: заготовлений набір даних, залогінений юзер, піднятий сервіс; у сучасних цим словом називають і сам механізм, що готує оточення й прибирає його. Стан (state) — це конкретна ситуація в системі, наприклад «існує замовлення в статусі paid». (precondition) — те, що мусить бути істинним ще до першого кроку тесту. Практично межа проста: фікстура — це інструмент, стан — результат, якого вона досягає, а передумова — вимога, яку цей стан задовольняє.
Чому arrange-фазу автотесту вважають підготовкою даних?
Бо саме в arrange тест приводить систему до потрібного стану, а це і є створення та налаштування даних. Питання ніколи не стоїть як «готувати чи ні» — без передумов перевіряти нема що. Реальні питання інші: звідки взяти ці дані (файл, генератор, API, БД) і як не дати їм протекти в наступний тест. Тому дизайн arrange — це насправді дизайн стратегії тест-даних, а не просто пара рядків перед expect.
Коли статична фікстура доречніша за генерацію, а коли навпаки?
Статична фікстура — це заготовлені значення у файлі чи дампі; її сила у детермінованості й читабельності: видно рівно те, на чому тест зелений. Її слабкість — колізії та тихе старіння: захардкоджений email ламається, щойно два тести або спробують створити його одночасно, а дамп із «активною підпискою» через місяць може стати «простроченим» після зміни правил бекенду. Генерація на льоту прибирає колізії й старіння, але коштує детермінованості: важче зрозуміти, на яких саме даних тест упав. Правильна відповідь — розподіл ролей, а не вибір одного. Довідкові стабільні дані (список країн, тарифи, конфіг фіч) тримають статичними, а унікальні сутності (юзери, замовлення) генерують. на кшталт порожнього рядка чи 4-байтного unicode закладають явно, бо генератор їх випадково не видасть.
Що таке фабрика тест-даних і навіщо їй дефолти з можливістю перевизначення?
Фабрика (factory) — це функція-конструктор: вона віддає повністю заповнений обʼєкт з осмисленими значеннями за замовчуванням, а тобі лишає перевизначити тільки ті поля, що суттєві для конкретної перевірки. Це родич патернів Builder і — причому щодо другого джерело майже ставить знак рівності: Object Mother названий там «just a catchy name for such a factory», тобто фабрикою стандартних наборів даних. Головна цінність не в «менше коду», а в читабельності за наміром: коли тест викликає buildUser({ role: 'admin' }), зрозуміло, що на результат впливає лише роль, а решта полів — випадковий фон. Виписуй той самий обʼєкт цілком, з усіма десятьма полями руками, — і вже не відрізниш, де значення принципове для перевірки, а де взяте абияк. Значення для полів фабрика зазвичай бере з генератора реалістичних даних.
Навіщо потрібен faker, якщо можна підставити рядок «test test»?
Faker — бібліотека для генерації правдоподібних значень: людські імена, поштові адреси, email, номери телефонів, UUID. Ця правдоподібність тут не косметика: ідеальне test test бреше про те, як виглядатимуть справжні значення, і ховає цілий клас багів. Довге прізвище з апострофом, нелатинські літери, адреса з незвичним форматом ловлять поламану верстку й помилки кодування, яких синтетично «чистий» рядок ніколи не покаже. Тобто faker не просто економить фантазію — він наближає тестові дані до бойових і робить їх ефективнішими на баг.
Чи гарантує faker унікальність значень, і чому це питання зі співбесіди?
Ні, і саме на цьому ловлять кандидатів. faker.internet.email() продукує випадкове, а не гарантовано унікальне значення: один і той самий рядок цілком може випасти вдруге, а на великій колізія настане неминуче — тут спрацьовує парадокс днів народження. Якщо покластися на «навряд чи збіжиться» для поля з унікальним ключем, рано чи пізно отримаєте 409 Conflict. Тому до таких ключів додають щось свідомо унікальне — таймстемп, номер воркера чи UUID. Faker відповідає за реалістичність, а за унікальність — окремий детермінований хвостик.
Навіщо задавати зерно faker і що це дає в CI?
Випадковість faker має зворотний бік — недетермінованість: кожен прогін дає інші значення, і відтворити червоний тест локально важко. Виклик faker.seed(123) фіксує стартову точку генератора, тож послідовність значень стає однаковою від прогону до прогону. Практичний прийом: у CI логувати використане , і коли тест впав, повторити рівно ту саму послідовність даних на своїй машині. Це компроміс, що дає різноманіття генерації й водночас відтворюваність фікстури.
Якими чотирма способами можна довести систему до потрібного стану і як обрати між ними?
Стан на кшталт «замовлення в статусі paid» готують через UI, через API, прямим записом у БД або через бекдор. Через UI — коли обʼєкт перевірки і є сам процес створення; у решті випадків це повільно й крихко. Через API — золотий стандарт: швидко, стабільно і, головне, працює крізь ту саму бізнес-логіку, якою користується жива людина, тож неможливих станів не породжує. Пряма БД — найшвидший шлях і єдиний, коли стан недосяжний ззовні (наприклад, треба «зістарити» запис), але обходить усі інваріанти застосунку. Бекдор — спеціальний тестовий як компроміс між API і БД. Правило вибору одне: готуй стан на найнижчому достатньому рівні — API за замовчуванням, а БД і бекдор лише свідомо, коли інакше ніяк.
Чому підготовку стану через API вважають золотим стандартом arrange-фази?
Тому що API виграє одразу за трьома осями. Швидкість: HTTP-запит створює передумову на порядок швидше, ніж клікання по формах. Стабільність: публічний API міняється рідше, ніж верстка, тож підготовка не ламається від кожного редизайну кнопки. І найважливіше — коректність: запит проходить через ту саму валідацію й бізнес-правила, що й дія реального користувача, тому й не народжує ситуацій, яких у продакшені бути не може. Вхід так само проганяють запитом раз на весь ран і далі перевикористовують збережену сесію, а не заповнюють форму логіну перед кожним тестом.
Які ризики у прямого запису в БД і в бекдора, і коли вони все ж виправдані?
Пряма БД дає швидкість ціною коректності: INSERT обходить валідацію, тригери й обчислювані поля, тож на світ зʼявляється стан, до якого реальний код системи ніколи б не дійшов — і тест зеленіє на неможливих даних. Додатковий мінус — жорстке звʼязування зі схемою: міграція таблиці ламає підготовку. Бекдор мʼякший (це контрольований тестовий ендпоінт, а не сирий запит), але несе власну : якщо він доїде до продакшену, це готова — обхід автентифікації чи створення адмінів. Обидва інструменти тримають як виняток: коли стан справді недосяжний через API, і з ясним розумінням, що ви обходите логіку продукту.
Чому бекдор-ендпоінт не повинен існувати в продакшені?
Бекдор (backdoor) — це ендпоінт, що дозволяє одним запитом привести систему в потрібний стан в обхід звичайних перевірок, наприклад POST /test/seed-order. Саме те, що робить його зручним у тестах, робить його небезпечним у проді: ендпоінт, який створює сутності без автентифікації й валідації, — це пряма діра в безпеці, через яку сторонній може наробити адмінів чи підробити дані. Тому бекдор вмикають виключно за прапорцем середовища й пильнують, щоб у продакшен-конфігурації він був недоступний. Правило коротке: тестовий бекдор існує лише в тестових середовищах.
Чим ізоляція на тест відрізняється від ізоляції на воркер?
Ізоляція на тест означає, що arrange кожного тесту сам заводить потрібні дані й нічого не успадковує від сусіда попереду — це просто інша грань незалежності тестів. Її порушення легко впізнати: сценарій зелений у складі всієї сюїти, але червоніє, щойно запускаєш його наодинці — насправді він підхопив дані сусіда. Ізоляція на воркер зʼявляється під паралеллю: коли тести йдуть у кількох процесах, їхні дані не мають колідувати між процесами, інакше два воркери, що реєструють один email, дадуть 409 на другому. Розвʼязань два: додати до даних ознаку воркера, щоб вони не збігалися, або виділити кожному воркеру окрему — свій акаунт, tenant чи простір імен. Перший рівень захищає від порядку й , другий — від паралельних процесів.
Тест зелений у повній сюїті, але червоний, коли запустити його окремо. Що це і в чому причина?
Це класичний симптом прихованого звʼязування через дані: тест мовчки покладається на стан, який створив хтось інший у сюїті. Наприклад, тест B робить assert на користувача, якого насправді завів тест A, тому наодинці B падає — потрібного юзера просто немає. Та сама поломка вилазить, якщо змінити порядок тестів або зашардувати сюїту між машинами. Лікування — не , а ізоляція на тест: кожен тест сам створює в arrange усе, що перевіряє, і нічого не очікує від сусідів. Якщо тест не виживає наодинці, він не незалежний, хоч би скільки разів був зеленим у наборі.
Як надійно прибирати тестові дані і чому «afterEach усе прибере» недостатньо?
Створені дані треба видаляти, бо інакше тестове сховище розбухає, простір унікальних ключів вичерпується, а майбутні прогони чіпляються за недоприбране. Стартовий інструмент — teardown у хуках (afterEach, afterAll) чи у фікстурі після use, але у нього є три дірки: тест упав посеред arrange і хук чистить за неповним списком; сам teardown кинув помилку; або воркер помер по чи OOM і хуки взагалі не виконались. Тому надійне рішення тримають на двох шарах: хук прибирає в штатному режимі, а поверх нього працює запобіжник, якому байдуже, чим скінчився тест. Роль такого запобіжника грає TTL (тестові сутності самі протухають і їх зносить фоновий ), нічне за домовленою ознакою, одноразові акаунти чи повне скидання стану. У Playwright teardown виконується навіть після падіння тесту, що закриває першу дірку, але від смерті воркера рятує тільки зовнішня страховка.
Що таке TTL і одноразові акаунти як страховка прибирання?
TTL (time to live) — обмежений час життя тестової сутності: вона позначається так, що фоновий джоб (чи сам застосунок) видаляє її після закінчення строку, тож осиротілі дані зникають самі, навіть якщо хук не спрацював. Одноразові акаунти — інший підхід до тієї ж проблеми: замість чистити стан, тест щоразу створює свіжий акаунт і не переймається його вмістом, а прибирає їх той самий TTL-джоб. Обидва прийоми знімають залежність прибирання від успіху конкретного тесту чи виживання воркера. Ключова ідея — не намагатися видалити рівно те, що створив, а забезпечити, щоб усе тестове врешті зникло за загальною ознакою.
Як писати тести під спільний стейджинг, який ділять кілька команд?
Спільний стенд підводить чотирма шляхами: чуже наповнення (хтось залив дві сотні замовлень — і перевірка «список має бути порожній» одразу червона), боротьба за єдиний ресурс (два прогони водночас хапають один акаунт — звідси черга й ), ламання стану прямо під час прогону (ручний тестувальник зніс юзера, на якому тримався сценарій) і дрейф конфігурації (ввімкнули — і поведінка попливла). Кодом тесту інфраструктуру не полагодити, тому рятує ізоляція на рівні даних: не спиратися на глобальний стан стенду. Замість припущення про порожній список тест сам заводить помічений власним префіксом запис і шукає рівно його серед чужих даних. Простими словами: писати тест так, ніби він на стенді не один, бо він там справді не один.
Чому не можна ганяти тести на дампі або копії продакшену?
Причин кілька, і кожної окремо достатньо. Юридична й приватна: у дампі лежать персональні дані (personally identifiable information, PII) справжніх користувачів — їхні імена, пошта, номери телефонів, подеколи платіжна інформація; щойно ця копія осідає в тестовій базі, логах чи на ноутбуці інженера, це вже обробка поза призначенням і потенційний витік, а GDPR наполягає на мінімізації даних і законній підставі, якої для такого тестування зазвичай бракує. Непередбачуваність: прод живий, і сутність, на якій тримався тест, можуть видалити чи змінити вночі платіжкою — тест стає недетермінованим за побудовою. мутації: найгірший варіант — тест не на копії, а на самому проді, і автоматизація «перевірки видалення» зносить дані реального клієнта. Потрібне «як прод», а не «прод»: синтетичні дані з faker і фабрик або анонімізований зріз, де PII замінено на фейкові зі збереженням форми й обсягу.
Що таке анонімізований зріз і чим він кращий за сирий прод-дамп?
Анонімізований (деперсоналізований) зріз — це копія структури прод-даних, у якій персональні поля замінено на фейкові з збереженням форми, розподілу й обсягу; цей процес називають маскуванням. Він дає те, заради чого взагалі тягнуться до продакшену, — реалістичний розподіл значень і , — але без чужих персональних даних у вашій базі, логах чи артефактах CI. На відміну від сирого дампа, тут немає юридичного ризику PII, і немає живих записів, які хтось змінить під ногами. Альтернатива тому самому — повністю синтетичні дані з фабрик і faker; вибір між ними залежить від того, наскільки важливо відтворити саме продакшн-розподіл, а не лише правдоподібну структуру.
Три кейси, де стратегія даних вирішує, зелений тест чи : фабрика з faker і гарантованою унікальністю, вибір рівня підготовки стану для чекауту, та ізоляція на із двошаровим . Скрізь — що робимо і чому саме так.
Кейс 1. Фабрика юзера: дефолти з faker, унікальність окремим хвостиком
Тесту потрібен адмін із балансом. Наївний варіант — тягнути повний обʼєкт із десятьма полями в кожен тест, і читач не розуміє, що з них суттєве. Фабрика прибирає цей шум: дефолти беремо з faker, а в тест виносимо тільки те, що впливає на перевірку.
import { faker } from '@faker-js/faker';
type User = { email: string; name: string; role: 'admin' | 'member'; balance: number };
// унікальний хвостик: faker дає реалістичність, а не унікальність
function uniqueEmail(): string {
return `qa+${Date.now()}-${faker.string.alphanumeric(6)}@test.local`;
}
function buildUser(overrides: Partial<User> = {}): User {
return {
email: uniqueEmail(),
name: faker.person.fullName(),
role: 'member',
balance: 0,
...overrides,
};
}
// у тесті видно намір: суттєві лише роль і баланс
const admin = buildUser({ role: 'admin', balance: 1000 });
Що дивитися і чому:
- Унікальність не довіряємо faker.
faker.internet.email()може повторитися, і на великій повториться напевно — це409 Conflictна другому тесті. Тому email збираємо з мітки часу й випадкового суфікса, а не покладаємось на «навряд чи збіжиться». ...overridesіде останнім. Спред у кінці дозволяє тесту перекрити будь-який дефолт; постав його на початок — і дефолти затруть значення, які передав тест.- Реалістичне імʼя ловить баги.
faker.person.fullName()дасть прізвище з апострофом чи нелатиницею, на яких провалиться крива верстка чи кодування — те, щоtest testзаховало б. - для відтворення. Якщо тест впав на дивному згенерованому імені,
faker.seed(N)на старті ран повторить ту саму послідовність локально; зерно варто логувати в CI.
Кейс 2. Той самий стан — чотири рівні. Що обрати для чекауту
Тесту чекауту потрібне замовлення в статусі paid. Спокуса зробити його кліками сильна, але дорога. Порівняння рівнів підготовки:
| Рівень | Швидкість | Стабільність | Поважає інваріанти | Коли обирати |
|---|---|---|---|---|
| UI | Найповільніше | Крихко | Так | Лише коли перевіряю сам флоу створення |
| API | Швидко | Висока | Так | За замовчуванням |
| Пряма БД | Найшвидше | Ламка на міграції | Ні, обходить логіку | Стан недосяжний ззовні (наприклад, «зістарити» запис) |
| Бекдор | Швидко | Середня | Частково | Компроміс, коли API незручний; тільки не в проді |
Для чекауту обʼєкт перевірки — оплата, а не створення замовлення, тож готуємо через API:
import { test, expect } from '@playwright/test';
test('чекаут оплаченого замовлення', async ({ page, request }) => {
// arrange: стан готуємо запитом, не кліками — швидко й крізь ту саму бізнес-логіку
const res = await request.post('/api/orders', {
data: { items: [{ sku: 'BOOK-1', qty: 1 }], status: 'paid' },
});
expect(res.status()).toBe(201);
const order = await res.json();
// act + assert: перевіряємо саме те, заради чого написаний тест
await page.goto(`/orders/${order.id}`);
await expect(page.getByTestId('order-status')).toHaveText('Оплачено');
});
Що дивитися і чому:
- UI лишаємо для одного випадку. Готувати замовлення кліками виправдано, тільки якщо перевіряємо сам процес оформлення; інакше це повільно, крихко й змішує підготовку з перевіркою.
- API поважає інваріанти. Запит проходить крізь валідацію застосунку, тож не створить стану, неможливого в проді. Прямий
INSERTтого ж замовлення міг би поставитиpaidбез запису про оплату — тест зеленів би на неможливих даних. - Пряма БД — для недосяжного. Її беруть, коли стан ззовні не зробити, наприклад треба замовлення з датою місячної давнини для тесту на прострочення. Ціна — жорстке звʼязування зі схемою: міграція таблиці ламає підготовку.
- Бекдор лишається за прапорцем середовища.
POST /test/seed-orderзручний, але в продакшені це діра в безпеці — його вмикають лише в тестових середовищах.
Кейс 3. Ізоляція на воркер плюс страховка на прибирання
Симптом «іноді падає в CI, локально зелено» часто означає, що два воркери деруться за той самий акаунт. Даємо кожному воркеру власний акаунт через worker-scoped й підстраховуємо прибирання, бо хук не виживе, якщо воркер помре.
import { test as base, expect } from '@playwright/test';
type Account = { id: string; email: string };
export const test = base.extend<{}, { account: Account }>({
account: [async ({}, use, workerInfo) => {
// власний акаунт на воркер: створюється раз, живе весь прогін воркера
const email = `w${workerInfo.workerIndex}-${Date.now()}@test.local`;
const account = await createAccount(email);
await use(account); // усі тести цього воркера працюють у ньому
await deleteAccount(account.id); // основний прибиральник по завершенні воркера
}, { scope: 'worker' }],
});
test('замовлення видно лише своєму акаунту', async ({ account }) => {
const order = await createOrder(account.id, { sku: 'BOOK-1' });
// шукаємо саме СВІЙ запис — не «список порожній» і не «рівно один»
const list = await listOrders(account.id);
expect(list.map((o) => o.id)).toContain(order.id);
});
Що дивитися і чому:
- Ключ воркера прибирає колізії між процесами.
workerInfo.workerIndexу email гарантує, що два паралельні воркери не полізуть в один акаунт — саме та колізія, що дає плаваючі409у CI. її не лікують, ізоляція лікує. - Спільний акаунт у межах воркера — прихована пастка. Тести одного воркера знову звʼязані: якщо вони ділять один кошик, то заважають одне одному. Тому кожен створює власне замовлення в межах акаунта, а не мутує спільний стан.
- Хук — лише перший шар.
deleteAccountпісляuseприбирає в нормі, але не спрацює при OOM чи воркера. Тому email містить домовлену ознаку (@test.local), за якою нічне прибирання або TTL- знесе незалежно від долі тесту. - шукає своє серед чужого.
toContain(order.id)виживе на спільному стенді, де є дані інших команд; асерт «рівно 1 замовлення» червонів би від першого чужого запису.
Словник і стратегія
- Розрізняю (fixture), стан (state) і (precondition): інструмент, досягнута ситуація і вимога до неї.
- Розумію, що arrange-фаза автотесту зводиться до готування даних і стану, і питання стоїть не «чи готувати», а «звідки брати» й «як не дати протекти».
Статичні фікстури проти генерації
- Знаю сильні сторони статичної фікстури (детермінованість, читабельність) і слабкі (колізії під паралель, тихе старіння даних).
- Розумію компроміс генерації: свіжі й безколізійні дані ціною детермінованості й проґавити edge-кейс.
- Знаю розподіл ролей: довідкові стабільні дані — статичні, унікальні сутності — генеровані, — закладені явно.
Фабрики і faker
- Можу пояснити, що фабрика (factory) — це дефолти плюс перевизначення, і її головна цінність — читабельність за наміром, а не «менше коду».
- Знаю пастку:
faker.internet.email()не гарантує унікальності, тож до унікальних ключів домішую мітку часу, індекс або UUID. - Можу пояснити, навіщо задавати faker (
faker.seed()) — щоб відтворити червоний прогін за тією самою послідовністю даних.
Підготовка стану
- Знаю чотири рівні підготовки стану — UI, API, пряма БД, бекдор — і компроміси кожного.
- Тримаю в голові правило найнижчого достатнього рівня: кліки — тільки для перевірки самого UI-флоу, типовий шлях — API (швидкість, стабільність, поважання інваріантів), а БД чи бекдор беру свідомо, як виняток.
- Розумію ризик прямого
INSERT: обходить валідацію й тригери, тож тест зелений на стані, який продукт ніколи не породив би.
Ізоляція
- Розрізняю ізоляцію на тест (кожен сам створює свої дані) та на воркер (дані не колідують між паралельними процесами).
- Можу пояснити симптом прихованого звʼязування: «проходить у сюїті, падає окремо» або залежить від порядку чи .
- Знаю два способи ізоляції на воркер: позначити дані ознакою воркера проти збігів або виділити кожному окрему (акаунт, tenant).
Прибирання
- Знаю три дірки наївного teardown: падіння посеред arrange, помилка в самому teardown, смерть воркера.
- Можу пояснити двошарову стратегію: хук прибирає штатно, а зовнішній запобіжник спрацьовує незалежно від того, чим завершився тест.
- Знаю варіанти страховки: TTL, нічне за ознакою, одноразові акаунти, повне скидання стану.
Спільні оточення і прод-дані
- Можу пояснити принцип «не покладайся на глобальний стан» на спільному стенді (чужі дані, гонки за ресурс, дрейф конфігурації): тест шукає власний маркований запис, а не «рівно 3 користувачі».
- Розумію три незалежні причини не брати прод-дані: PII і GDPR, непередбачуваність та старіння, ризик мутації живих записів.
- Знаю альтернативи «як прод, а не прод»: синтетичні дані з фабрик і faker або анонімізований (маскований) зріз.
Квіз
Перед стартом
- Питань: 12
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Фікстура, стан, передумова — у чому різниця?


