Верифікація результатів AI
Зміст
У попередній главі Генерація тест-артефактів ми навчилися просити модель писати тест-кейси, автотести й дані. Ця глава — про другу, важливішу половину роботи: як не повірити вигляду. AI-вивід майже завжди читається впевнено, охайно відформатований і компілюється — і саме ця гладкість присипляє пильність. Тест зеленіє, код запускається, баг-репорт звучить професійно — а перевіряє тест порожнечу, код кличе неіснуючий метод, а в репорті — вигаданий заголовок. Верифікація AI-результатів — це навичка не довіряти виводу з першого погляду, і на співбесідах її питають прямо: «як ви перевіряєте те, що згенерував AI?».
Ця глава працює в парі з попередньою: генерація без верифікації — незавершена робота. Щойно ви реально використовуєте AI для генерації тестів чи коду, ця навичка не опційна — без неї ви чужі помилки під своїм імʼям.
Чому «виглядає правильно» — це пастка
Мовна модель оптимізована передбачати правдоподібний наступний токен, а не казати правду. Механіку розібрано в главі Як працюють LLM; тут важливий наслідок: модель однаково впевнено видає і коректну відповідь, і (hallucination) — вигадку, подану як факт. Впевненість тону не корелює з правильністю. Немає внутрішнього індикатора «тут я не знаю»: page.waitForText() вона напише з тією ж інтонацією, що й реальний page.waitForSelector().
Проблема глибша за саму модель — вона у вашій голові. Чим полірованіший вивід, тим нижча пильність: спрацьовує (automation bias) — схильність довіряти пропозиції системи більше, ніж власній перевірці. Ви очікували робочий код, отримали щось схоже на робочий код — і мозок радо ставить галочку. Для QA це особливо небезпечно, бо весь фах збудований на протилежному рефлексі: не вірити, доки не перевірив.
Ключова теза глави: гарний вигляд, охайне форматування, успішна компіляція і навіть зелений прогін — це сигнали правдоподібності, а не правильності. Кожен із них можна отримати, нічого корисного не перевіривши.
Типові дефекти AI-згенерованого коду
Перш ніж говорити «як перевіряти», треба знати, що ламається найчастіше. AI-код має впізнаваний набір дефектів.
Зелені тести, що нічого не перевіряють. Найпідступніший клас. Тест проходить, метрика росте — а перевірки або немає, або вона тавтологічна. Типове:
// Виглядає як тест логіну. Насправді не перевіряє нічого.
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'secret');
await page.click('button[type=submit]');
// ← тут мав бути expect. Його немає. Тест зелений завжди.
});
Той самий дефект у витонченішій формі — забутий await перед web-first Playwright або перевірка, яка істинна за побудовою:
await expect(page.locator('.title')).toBeVisible(); // забули await → проміс не дочекано, помилка проковтнута
expect(true).toBeTruthy(); // тавтологія: не впаде ніколи
Вигадані API й селектори. Модель компонує імена методів зі шматків, які бачила, і легко вигадує неіснуючі: page.waitForText(), cy.getByRole() (у Cypress такого немає), опції конфіга, яких немає в документації. Так само з : getByRole('button', { name: 'Зберегти' }) виглядає бездоганно, але якщо в реальному DOM цієї ролі чи назви немає — селектор не знайде нічого. Модель не бачила вашої сторінки; вона вгадала розмітку.
Поверхневі перевірки. Покрито лише . Перевіряється присутність елемента, а не його коректність: що кнопка є — але не що вона робить правильну дію; що прийшов 200 — але не що в тілі відповіді правильні дані.
Застарілі патерни. waitForTimeout(3000) замість web-first очікування, виклики застарілих API, стиль із туторіалів пʼятирічної давнини, які потрапили в тренувальні дані.
Три опори верифікації
Перевірка AI-виводу тримається на трьох діях: прочитати як рев'юер, виконати як реальність, звірити факти з першоджерелом. Жодна поодинці не достатня.
Рев'ю виводу як навичка
Читайте AI-вивід так, як ревʼюили б джуна — тільки скептичніше. У джуна є контекст задачі й совість сказати «я тут не впевнений»; у моделі немає ні того, ні того. Це та сама навичка, що й статичне рев'ю коду в класичному QA, лише обʼєкт — машинний вивід.
Головне питання до тесту: що станеться, якщо фіча зламається — цей тест почервоніє? Подумки «мутуйте» застосунок: приберіть кнопку, інвертуйте валідацію, поверніть неправильні дані — чи впіймає це тест? Якщо ні — тест декоративний. Друге питання: чого цей тест НЕ ловить? happy path без негативних перевірок, відсутні , перевірка присутності замість перевірки значення.
Для не-кодового виводу (тест-кейси з вимог, аналіз багів) рев'ю таке саме: чи всі кроки відтворювані, чи очікуваний результат — це справді інваріант вимоги, а не переказ дії, чи не проґавлені негативні сценарії.
Виконання як перевірка
Найдешевша перевірка реальністю — просто запустити. Компілюється? Селектор резолвиться в живому DOM? Тест доходить до кінця? Це відсіює вигадані API й селектори за секунди — те, чого рев'ю очима може не помітити.
Але виконання має межу, і це найважливіший пункт глави: зелений прогін не доводить, що тест щось перевіряє. Порожній тест із прикладу вище зелений завжди. Тому виконання доповнюють однією дією — зламати навмисне. Змініть очікуване значення на завідомо неправильне, закоментуйте фічу в застосунку, підмініть відповідь. Якщо тест лишається зеленим — він фальшивий. Справжній доказ якості тесту не в тому, що він проходить, а в тому, що він червоніє, коли має.
// Перевірка тесту: тимчасово ламаємо очікування
await expect(page.locator('.balance')).toHaveText('1000'); // було
await expect(page.locator('.balance')).toHaveText('НЕПРАВИЛЬНО'); // тест мусить впасти
Якщо після цієї підміни тест усе одно зелений — асерт не працює (забутий await, тавтологічне очікування або асерт, до якого виконання взагалі не доходить), і зеленому оригіналу гріш ціна.
Верифікація фактів
Для тверджень, а не коду, працює інше правило: звіряй із першоджерелом, а не з тією ж моделлю. Коли AI називає номер версії, назву HTTP-заголовка, сигнатуру методу чи — це рівно те, що модель галюцинує найохочіше. Перевіряйте проти канонічного джерела: офіційна документація інструмента, специфікація, реальний код застосунку.
Критична пастка: не перепитуйте ту саму модель «ти впевнений?». Вона не має нового джерела — тільки той самий розподіл імовірностей і той самий bias. Часто вона або впевнено підтвердить вигадку, або з тією ж упевненістю розвернеться на 180° без жодної нової інформації. І те, і те — шум. Реальна перевірка приходить лише ззовні: відкрити доку, запустити код, глянути в DevTools.
Приклад. Модель радить expect(page).toHaveText('Готово'). Виглядає розумно. Але за документацією Playwright toHaveText існує для локатора, не для page. Правильно — expect(page.locator('...')).toHaveText(...). Знайти це можна лише в доці, а не перепитуванням моделі.
Definition of Done для AI-виводу
Верифікація стає повторюваною, коли є чек-лист «готово». Для AI-згенерованого коду він не мʼякший за ваш звичайний DoD — він той самий:
- Компілюється і запускається без вигаданих API, опцій, імпортів.
- Селектори резолвляться в реальному DOM (див. DOM і селектори).
- Тест червоніє, коли фічу зламано (перевірено навмисною поломкою).
- Покрито не лише happy path: є негативні й граничні перевірки з вимоги.
- Факти (версії, заголовки, коди, сигнатури) звірені з першоджерелом.
- Немає протікання секретів чи PII у код або в .
- Відповідає конвенціям проєкту, а не абстрактному стилю з інтернету.
Якщо (JSON за схемою) генерується для інструментів — його валідують проти схеми програмно, не очима; про це докладніше в главі Tool use і function calling. А коли перевіряти доводиться масово й регулярно — це вже територія Evals та LLM-as-judge і Тестування AI-застосунків.
Відповідальність — твоя
Останнє й найважливіше. У коміті стоїть ваше імʼя, не імʼя моделі. «AI так написав» — не аргумент ні на код-рев'ю, ні на пост-мортемі після інциденту. Модель не несе відповідальності, не памʼятає контексту й не постане перед командою — усе це лягає на людину, яка натиснула «закомітити».
Найздоровіша ментальна модель: AI — це дуже швидкий джун, чию роботу ви апрувите своїм підписом. Швидкість він дає вам справжню; відповідальність за якість лишає вам повністю. Верифікація — це і є та точка, де ви цей підпис ставите свідомо.
Типові помилки
- Тест зелений — виглядає як робоче покриття, а насправді асерт відсутній або забутий
awaitпроковтнув помилку: тест не впаде ніколи. - Код компілюється — виглядає як коректний, а насправді кличе метод, що існує лише «у світі моделі» (
page.waitForText,cy.getByRole), і впаде на першому реальному виклику. - Модель упевнено назвала версію/заголовок/код — виглядає як факт, а насправді галюцинація, яку видно лише при звірці з документацією.
- «Перепитав модель — вона підтвердила» — виглядає як верифікація, а насправді те саме джерело з тим самим bias; нової інформації нуль.
- Селектор
getByRole('button', { name: '…' })— виглядає бездоганно, а насправді елемента з такою роллю чи назвою в DOM немає, бо модель не бачила сторінки. - AI покрив happy path — виглядає як готовий набір тестів, а насправді негативні й граничні сценарії відсутні, і реальні баги пройдуть повз.
Підсумок
- Гладкість не дорівнює правильності: охайний, впевнений, скомпільований вивід нічого не гарантує.
- Зелений тест — не доказ; доказ — що тест червоніє, коли фіча ламається. Ламайте навмисне.
- Виконання ловить вигадані API й селектори, але не порожні перевірки — потрібні всі три опори: рев'ю, виконання, факти.
- Факти звіряй із першоджерелом, а не перепитуванням тієї ж моделі.
- DoD для AI-коду дорівнює твоєму звичайному DoD; відповідальність за вивід — на тобі, не на моделі.
Можливі питання
- «Як ви перевіряєте код, згенерований AI?» — інтервʼюер хоче почути, що зелений прогін вас не переконує: ви читаєте вивід критично, ламаєте тест навмисне, звіряєте API з документацією. Відповідь «запускаю, і якщо зелений — ок» — червоний .
- «AI написав тест, він проходить. Цього достатньо?» — пастка на розуміння різниці «проходить» vs «перевіряє». Сильна відповідь: ні, спершу переконаюся, що тест падає, коли фічу зламано.
- «Був випадок, коли AI вас підвів?» — поведінкове питання; готуйте конкретну історію: вигаданий метод, селектор під неіснуючий елемент, галюцинований заголовок — і як ви це впіймали.
- «Хто відповідає за баг у AI-згенерованому коді, що доїхав у прод?» — перевірка зрілості. Правильна рамка: той, хто закомітив; AI не суб'єкт відповідальності.
На що дивиться інтервʼюер: чи трактуєте ви AI-вивід як чернетку на рев'ю, а не як істину; чи усвідомлюєте упередження автоматизації; чи маєте конкретний, повторюваний спосіб перевірки, а не «на око».
Джерела
Чому «виглядає правильно» — це пастка
- ISTQB CT-GenAI Syllabus v1.1 — найкоротша форма тези в каноні: текст статистично правдоподібний, але
But plausible is not necessarily correct. - ISTQB CT-AI Syllabus v2.0 — матеріальна підстава: моделі схильні до впевнених, але неправильних передбачень, і це наслідок даних, а не рідкісний збій.
- Brown et al. — Language Models are Few-Shot Learners (arXiv:2005.14165) — виміряний бік проблеми в голові читача: люди важко відрізняють згенерований текст від написаного людиною.
- Claude Code Docs — Best practices for Claude Code — той самий автоматизм у дозволах: після десятого підтвердження ви вже не рев'юїте, а клікаєте.
Типові дефекти AI-згенерованого коду
- Playwright — Writing tests — означення тесту (дії плюс асерти про стан) і механіка забутого
await: асинхронні матчери його потребують, загальні — ні. - Playwright — Assertions — канонічний перелік справжніх асертів і поділ на ретрайні та неретрайні, через які тест і стає .
- Playwright — Best Practices — чому
waitForTimeoutє застарілим патерном: ставка на web-first перевірки й замість ручних пауз. - Claude Code Docs — Best practices for Claude Code —
trust-then-verify gap: правдоподібна на вигляд реалізація, яка не покриває .
- Claude Code Docs — Best practices for Claude Code — чим саме буває перевірка (, лінтер, скрипт-дифф, ), правило
If you can't verify it, don't ship itі вимога просити доказ, а не заяву про успіх. - ISTQB CT-GenAI Syllabus v1.1 — канонічні способи виявлення окремо під кожен клас дефекту (перехресна звірка, логічна валідація, виконання виводу) і глибина перевірки пропорційно .
- Playwright — Writing tests — межа виконання дослівно: воно ловить вигадані API за секунди, але не порожні перевірки; там же — що
toHaveTextіснує для локатора, а не дляpage. - ISTQB CTFL Syllabus v4.0 — рев'ю як структурований, але гнучкий процес із заданими цілями й : та сама дисципліна, тільки обʼєкт машинний.
- ISTQB CT-AI Syllabus v2.0 — чому «перепитати ту саму модель» не є перевіркою: другий оцінювач на тій самій технології відтворює ті самі упередження й помилки.
Definition of Done для AI-виводу
- Claude Code Docs — Best practices for Claude Code — найсильніша модальність доки під цим чек-листом:
Always provide verification (tests, scripts, screenshots). If you can't verify it, don't ship it. - Claude Code Docs — Run Claude Code programmatically (headless) — межа програмної валідації: схему можна закріпити на рівні API, але
formatтрактується як анотація й не примушується. - Playwright — Best Practices — орієнтир для пункту про конвенції та стабільні локатори, проти якого рев'юють згенерований тест.
- ISTQB CT-GenAI Syllabus v1.1 — гейт із модальністю MUST і прив'язкою глибини перевірки до оціненого рівня ризику.
- ISTQB CT-GenAI Syllabus v1.1 — людська оцінка названа необхідною без пом'якшень:
Human evaluation is essential for ensuring quality and accuracy of GenAI-powered test tasks. - Claude Code Docs — Best practices for Claude Code — зворотний бік: без зовнішньої перевірки єдиний сигнал зупинки — «схоже на готове», і петлею верифікації стаєте ви.
Чому «виглядає правильно» ще не означає «правильно», коли йдеться про AI-вивід?
Бо мовна модель оптимізована не під істину, а під правдоподібний наступний токен. Гладке форматування, впевнений тон і код, що компілюється, — це властивості форми відповіді, а не доказ її змісту. Проблема в тому, що всі ці зовнішні ознаки можна отримати, нічого корисного не перевіривши: тест зеленіє з порожнім тілом, код запускається з викликом неіснуючого методу, баг-репорт звучить професійно з вигаданим заголовком. Для QA це прямий конфлікт із фаховим рефлексом «не вірю, доки не перевірив»: тут перевіряти треба саме тому, що вивід переконливий. Практичний висновок — жоден із сигналів вигляду (охайність, компіляція, зелений прогін) не замінює власної верифікації.
Що таке галюцинація (hallucination) моделі й чому впевненість тону тут нічого не гарантує?
— це вигадка, подана з тією ж інтонацією, що й факт: модель називає неіснуючий метод, вигаданий заголовок чи хибний так само спокійно, як і правильні. Механізм у тому, що в моделі немає внутрішнього маркера «тут я не впевнена» — вона генерує і правду, і вигадку з одного розподілу ймовірностей, і рівень її «впевненості» жодним чином не корелює з коректністю. Тому спроба зчитати надійність відповіді з її тону — самообман. page.waitForText() вона напише рівно так само переконливо, як реальний page.waitForSelector(). Єдиний спосіб відділити факт від галюцинації — звірка із зовнішнім джерелом, а не оцінка того, наскільки впевнено це звучить.
Що таке упередження автоматизації (automation bias) і чому воно особливо небезпечне для тестувальника?
Це схильність людини довіряти підказці системи більше, ніж власній перевірці, — і що полірованіший вивід, то сильніше воно спрацьовує. Коли результат виглядає рівно так, як ти сподівався, мозок сам закриває задачу — і крок перевірки непомітно випадає. Небезпека для QA у тому, що весь фах збудований на протилежному: сумніватися за замовчуванням. Automation bias тихо вимикає цей рефлекс саме там, де він найпотрібніший, — над згенерованим виводом, який виглядає готовим. Практичний наслідок: до AI-виводу треба свідомо застосовувати ту саму недовіру, що й до будь-якого непротестованого коду, а не знижувати планку через його охайність.
Які дефекти найчастіше зустрічаються в AI-згенерованому коді?
Їх чотири впізнаваних класи. Перший і найпідступніший — зелені тести, що нічого не перевіряють: відсутній, забутий, або тавтологічний. Другий — вигадані API й селектори: модель компонує назви методів зі шматків, які бачила, і легко видає неіснуючі (page.waitForText, cy.getByRole), а будує під розмітку, якої не бачила. Третій — поверхневі перевірки: покрито лише , перевіряється присутність елемента замість коректності його поведінки. Четвертий — застарілі патерни: waitForTimeout замість web-first очікування, виклики застарілих API, стиль зі старих туторіалів, що осіли в тренувальних даних. Знати цей набір важливо, бо перевірка починається з розуміння, що саме тут зазвичай ламається.
Що таке «зелений тест, який нічого не перевіряє», і як він виникає?
Це тест, що стабільно проходить, піднімає метрику , але не має реальної перевірки поведінки. Виникає він кількома шляхами: асерт просто забули дописати (тест доходить до кінця й завершується успіхом); асерт тавтологічний (expect(true).toBeTruthy() не впаде ніколи); або перед web-first асертом Playwright забули await, і не дочекано — помилку проковтнуто, тест зелений. Підступність у тому, що зовні це виглядає як нормальне покриття, а насправді перевіряється порожнеча. Виявити такий тест виконанням не можна — він зелений завжди; ловиться він лише навмисною поломкою або уважним рев'ю.
Чому забутий await перед асертом Playwright робить перевірку фальшивою?
Web-first асерти Playwright (expect(locator).toBeVisible() тощо) асинхронні й повертають проміс. Якщо не поставити перед ними await, тест не дочекається результату перевірки: проміс лишається без обробника, а можливий реджект просто губиться, не валячи тест. Зовні асерт присутній, код читається як коректний — але фактично він не впливає на долю тесту. Наслідок: тест буде зеленим навіть тоді, коли реальна умова хибна. Саме тому забутий await — класичний спосіб отримати фальшиво-зелений тест, і саме тому перевірка через навмисну поломку його викриває: підмінене очікування все одно не завалить тест.
Чому виконання ловить вигадані API й селектори, а рев'ю очима — не завжди?
Бо вигаданий метод чи неіснуючий селектор проявляються в рантаймі миттєво: код або не компілюється, або падає на першому виклику, або локатор не резолвиться в живому DOM. Оком таку помилку легко проґавити — page.waitForText() виглядає абсолютно правдоподібно поруч зі справжніми методами, а getByRole('button', { name: 'Зберегти' }) синтаксично бездоганний, хоч елемента з такою роллю на сторінці може й не бути. Модель не бачила ні реальної бібліотеки в її поточній версії, ні твоєї сторінки — вона вгадала і те, і те. Тому найдешевша перевірка проти галюцинованих імен — просто запустити: секунда рантайму відсіює те, на що рев'ю могло б не спіткнутися.
На яких трьох опорах тримається верифікація AI-виводу?
На рев'ю, виконанні та звірці фактів — і жодна поодинці не достатня. Рев'ю — прочитати вивід критично, як , і спитати, що він НЕ ловить. Виконання — запустити й переконатися, що код живий, а тест реально червоніє, коли має. Звірка фактів — перевірити всі конкретні твердження (версії, заголовки, сигнатури) проти першоджерела. Кожна опора закриває сліпу зону іншої: рев'ю може не помітити вигаданого методу, який ловить виконання; виконання не викриває порожнього тесту, який видно навмисною поломкою; а факт про статус-код не перевірити ні тим, ні тим — лише документацією. Сильна відповідь на співбесіді якраз і показує, що кандидат застосовує всі три, а не покладається на одну.
Чим рев'ю AI-виводу відрізняється від рев'ю коду джуна?
За технікою — нічим, за налаштуванням скепсису — суттєво. Це та сама навичка статичного рев'ю: читаєш чужий код і питаєш, що з ним не так. Різниця в тому, що у джуна є контекст задачі й совість чесно сказати «тут я не впевнений», а в моделі немає ні того, ні іншого — вона однаково впевнено віддасть і правильне рішення, і повну вигадку. Тому AI-вивід треба ревʼювати ще прискіпливіше, ніж код живої людини. Практично це означає читати згенероване як чернетку на рев'ю, а не як готову відповідь, і не робити знижку на те, що воно охайно виглядає.
Яке головне питання треба поставити до згенерованого тесту під час рев'ю?
Головне — «а якщо фіча зламається, цей тест почервоніє?». Щоб відповісти, подумки «мутуй» застосунок: прибери кнопку, інвертуй валідацію, поверни неправильні дані — і перевір, чи впіймає це тест. Якщо ні — тест декоративний, він лише створює ілюзію покриття. Друге за важливістю питання — «чого цей тест НЕ ловить?»: чи є негативні сценарії, чи покрито , чи не підмінено перевірку значення перевіркою присутності. Ці два питання відділяють тест, що захищає поведінку, від тесту, що просто зеленіє.
Чому зелений прогін не доводить, що тест щось перевіряє?
Бо «проходить» і «перевіряє» — різні речі. Порожній тест без асерту, тест із забутим await чи з тавтологічним очікуванням зелений завжди — незалежно від того, працює фіча чи ні. Тобто зелений колір може означати як «поведінка коректна», так і «перевірки просто немає», і за самим фактом проходження ці два випадки не розрізнити. Ось чому виконання, хоч і ловить вигадані API, має принципову межу: воно не бачить порожнечі всередині тесту. Реальний доказ якості тесту — не те, що він проходить, а те, що він падає, коли має падати.
Що означає «зламати навмисне» і навіщо це роблять із AI-згенерованим тестом?
Це прийом перевірки самого тесту: тимчасово вносиш у систему завідомо неправильний стан і дивишся, чи тест на це відреагує. Практично — підміняєш очікуване значення на явно хибне (toHaveText('НЕПРАВИЛЬНО')), коментуєш фічу в застосунку або підмінюєш відповідь, і запускаєш. Якщо тест червоніє — асерт справді працює, тесту можна вірити. Якщо після поломки він усе одно зелений — перевірка мертва (забутий await, тавтологія або асерт, до якого виконання не доходить), і зеленому оригіналу вірити не можна. Це єдиний спосіб відрізнити тест, що захищає поведінку, від фальшиво-зеленого, бо звичайний прогін обидва показує однаково.
Модель назвала статус-код, версію бібліотеки й сигнатуру методу. Як це верифікувати?
Тільки звіркою із зовнішнім першоджерелом — офіційною документацією, специфікацією чи реальним кодом застосунку, — а не довірою до самої відповіді. Конкретні факти (номери версій, назви заголовків, статус-коди, сигнатури) — рівно та категорія, яку модель галюцинує найохочіше, бо тут немає простору для «приблизно правильно»: метод або існує, або ні. Механізм перевірки простий: відкрити доку інструмента, запустити код, глянути в DevTools — тобто дістати незалежне джерело істини. Важливо, що перевіряти треба навіть ті факти, які звучать абсолютно впевнено, — саме впевненість тут оманлива. Знайти помилку на кшталт «toHaveText існує для локатора, а не для page» можна лише в документації, не в самому діалозі з моделлю.
Чому не можна перевіряти факт, перепитавши ту саму модель «ти впевнений?»
Бо повторне питання не дає моделі нового джерела — лише той самий розподіл імовірностей і той самий bias. У відповідь вона так само впевнено може й підтвердити вигадку, й відмовитися від коректної відповіді — нової інформації в обох випадках нуль, тож це шум, а не верифікація. Це створює ілюзію перевірки: діалог виглядає як діалог із експертом, який щойно передумав, хоча насправді нічого не звірялося. Реальна перевірка завжди приходить іззовні контексту моделі: документація, запуск коду, погляд у DevTools. Тому «перепитав — вона підтвердила» на співбесіді читається як нерозуміння того, звідки береться надійність відповіді.
Що входить у Definition of Done для AI-згенерованого коду?
Той самий DoD, що й для будь-якого свого коду, — його не пом'якшують через те, що написала машина. Ключові пункти: код компілюється й запускається без вигаданих API, опцій та імпортів; селектори резолвляться в реальному DOM; тест червоніє, коли фічу зламано (перевірено навмисною поломкою); покрито не лише happy path, а й негативні та граничні сценарії з вимоги; усі факти звірені з першоджерелом; немає протікання секретів чи персональних даних у код або ; стиль відповідає конвенціям проєкту, а не абстрактному коду з інтернету. Сенс списку — зробити верифікацію повторюваною: замість «на око» є конкретний набір критеріїв, який проходить будь-який згенерований шматок перед .
Як перевіряти структурований вивід моделі (наприклад, JSON за схемою)?
Програмно, валідацією проти схеми, а не очима. Коли модель генерує структуровані дані для інструмента (JSON під певний формат), візуальна перевірка ненадійна: пропущене поле чи неправильний тип легко проґавити в довгому об'єкті, а цих даних впаде вже в проді. Тому такий вивід проганяють через , який детерміновано ловить невідповідності. Це частина ширшого принципу: усе, що можна перевірити автоматично й детерміновано, не варто перевіряти вручну. А коли перевіряти доводиться масово й регулярно — це вже задача для evals та підходу LLM-as-judge, а не для ручного рев'ю кожної відповіді.
Хто відповідає за баг в AI-згенерованому коді, що доїхав у прод?
Той, хто натиснув «закомітити», — людина, чиє ім'я стоїть у коміті, а не модель. «AI так написав» не працює як аргумент ні на код-рев'ю, ні на пост-мортемі: модель не суб'єкт відповідальності, вона не пам'ятає контексту й не постане перед командою. Уся відповідальність за якість лягає на того, хто вивід прийняв і підписав своїм комітом. Найздоровіша рамка тут — сприймати AI як дуже швидкого джуна: швидкість він дає справжню, але апрув його роботи — і наслідки цього апруву — повністю на тобі. Верифікація — це і є та точка, де ти цей підпис ставиш свідомо, а не автоматично.
Три кейси з життя QA, який щодня приймає AI-вивід: розбір фальшиво-зеленого тесту й перевірка його навмисною поломкою, полювання на вигаданий метод та селектор через виконання й документацію, і таблиця «який сигнал чого насправді вартий». Скрізь — що подивитися і чому.
Кейс 1. Фальшиво-зелений тест: прочитати, зламати, повірити
Ти попросив модель написати e2e-тест логіну. Вона віддала охайний код, він проходить у CI. Перш ніж — прочитай його як джуна, а не як готову відповідь.
import { test, expect } from '@playwright/test';
test('користувач логіниться', async ({ page }) => {
await page.goto('/login');
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'secret');
await page.click('button[type=submit]');
expect(page.getByTestId('dashboard')).toBeVisible(); // ← пастка
});
Головне питання рев'ю: якщо логін зламається, цей тест почервоніє? Тут — ні. Перед expect(...).toBeVisible() забули await, тож web-first повертає , якого ніхто не дочекався: перевірка не встигає вплинути на долю тесту, і він зелений навіть тоді, коли дашборд не з'явився. Зовні асерт присутній, код читається коректно — але захисту немає.
Доказ здобувають не рев'ю, а навмисною поломкою. Тимчасово підмінюємо очікування на завідомо неправильне й запускаємо:
expect(page.getByTestId('НЕ-ІСНУЄ')).toBeVisible(); // міняємо ЛИШЕ локатор, форму не чіпаємо — тест МУСИТЬ впасти
Що дивитися і чому:
- Тест лишився зеленим після підміни — асерт мертвий. Це остаточний доказ, що перевірка не працює: справжній асерт на неіснуючий завалив би тест. Причина тут — забутий
await; той самий симптом дає тавтологічне очікування або асерт, до якого виконання взагалі не доходить. - Виправлення — повернути
awaitі перезапустити поломку. Правильний тест на підміні падає, на оригінальному значенні — проходить. Тільки після цих двох прогонів зеленому кольору можна вірити. - Зелений прогін сам собою нічого не довів. Порожній чи фальшивий тест зеленіє завжди; відрізнити його від справжнього можна лише поломкою, не спостереженням «ну він же проходить».
Кейс 2. Вигаданий метод і селектор: виконання плюс документація
Модель запропонувала «швидкий» варіант перевірки тексту й підказала локатор під кнопку. Виглядає бездоганно:
// порада моделі
await expect(page).toHaveText('Готово');
await page.getByRole('button', { name: 'Зберегти' }).click();
Обидва рядки — типова в охайній обгортці, і ловляться вони різними опорами.
Перший рядок відсіює звірка з документацією. toHaveText існує для локатора, а не для об'єкта page — за офіційним переліком асертів Playwright такого поєднання немає. Перепитувати модель «ти впевнений?» тут марно: нового джерела вона не має й може з тією ж упевненістю підтвердити вигадку. Правильна форма береться з доки:
await expect(page.locator('.status')).toHaveText('Готово');
Другий рядок відсіює виконання. Синтаксично getByRole('button', { name: 'Зберегти' }) бездоганний, але модель не бачила твоєї сторінки — якщо в реальному DOM немає елемента з роллю button і такою назвою, локатор не знайде нічого й тест впаде на кліку. Секунда рантайму показує це швидше за будь-яке вчитування:
// перевірка гіпотези про селектор до того, як писати весь тест
const btn = page.getByRole('button', { name: 'Зберегти' });
console.log(await btn.count()); // 0 → ролі/назви в DOM немає, локатор вигаданий
Що дивитися і чому:
- Різні дефекти — різні опори. Неіснуючий метод ловить документація (рев'ю очима міг би проґавити), неіснуючий селектор — виконання в живому DOM. Одна опора не закриває обидва.
count()як дешевий зонд. Нуль елементів означає, що прив'язка вигадана; далі локатор звіряють із реальною розміткою, а не з тим, «як зазвичай називають кнопку».- Джерело істини — зовні моделі. І доку, і DOM дістаєш незалежно; повторний діалог із моделлю нового факту не приносить.
Кейс 3. Таблиця: який сигнал чого насправді вартий
Уся глава зводиться до одного розрізнення — правдоподібність проти правильності. Ось як типові «зелені» сигнали співвідносяться з тим, що вони реально доводять, і чим їх добивати.
| Сигнал від AI-виводу | Що здається | Що доведено насправді | Чим перевірити |
|---|---|---|---|
| Код компілюється | «Він коректний» | Лише що синтаксис валідний | Запустити; звірити API з докою |
| Тест зелений | «Фіча покрита» | Нічого — порожній тест теж зелений | Зламати навмисне: тест мусить почервоніти |
| Селектор виглядає правильно | «Знайде елемент» | Нічого — модель не бачила DOM | Виконати; count() у живій розмітці |
| Модель назвала версію/заголовок/код | «Це факт» | Нічого — улюблена зона галюцинацій | Першоджерело: доку, спека, DevTools |
| «Перепитав — підтвердила» | «Верифіковано» | Нічого — те саме джерело, той самий bias | Незалежне джерело поза моделлю |
| Покрито happy path | «Набір тестів готовий» | Лише позитивний сценарій | Дописати негативні й граничні з вимоги |
Що дивитися і чому:
- Ліва колонка — це все ознаки форми. Кожен рядок «здається» — про вигляд, «насправді» — про зміст; збіг між ними не гарантований.
- Перевірка завжди в правій колонці. Вона або запускає код, або дістає зовнішнє джерело — тобто виходить за межі самого виводу; усе, що лишається всередині діалогу з моделлю, верифікацією не є.
- DoD для AI-коду = твій звичайний DoD. Компіляція без вигаданих API, резолвні селектори, червоніння на поломці, негативні й граничні кейси, звірені факти, жодних секретів у коді чи — і тільки тоді із твоїм ім'ям.
Чому «виглядає правильно» — пастка
- Розумію, що модель оптимізована під правдоподібний токен, а не під істину, тож впевнений тон не корелює з коректністю.
- Можу пояснити, що охайність, компіляція і зелений прогін — сигнали правдоподібності, а не правильності, і кожен дається без реальної перевірки.
- Знаю, що таке (hallucination): вигадка, подана з тією ж інтонацією, що й факт, без внутрішнього маркера «тут я не впевнена».
- Розумію (automation bias) і чому воно найнебезпечніше саме для QA — вимикає рефлекс «не вірю, доки не перевірив».
Типові дефекти AI-коду
- Знаю чотири класи дефектів: зелені тести без перевірки, вигадані API/селектори, поверхневі перевірки, застарілі патерни.
- Можу назвати три способи отримати фальшиво-зелений тест: відсутній , тавтологія (
expect(true).toBeTruthy()), забутийawaitперед web-first асертом. - Можу пояснити, чому модель вигадує методи (
page.waitForText,cy.getByRole) і селектори під розмітку, якої не бачила. - Знаю різницю «перевірка присутності vs перевірка коректності»: що кнопка є — не те саме, що вона робить правильну дію.
Три опори верифікації
- Можу назвати три опори — рев'ю, виконання, звірка фактів — і пояснити, чому жодна поодинці не достатня.
- Розумію, що рев'ю AI-виводу — це статичне рев'ю з підвищеним скепсисом, бо в моделі немає ні контексту задачі, ні совісті сказати «не знаю».
- Знаю головне питання до тесту: «якщо фіча зламається, він почервоніє?» — і друге: «чого цей тест НЕ ловить?».
- Розумію межу виконання: воно ловить вигадані API й селектори, але зелений прогін не доводить, що тест щось перевіряє.
- Можу пояснити прийом «зламати навмисне»: підмінити очікування на хибне і впевнитися, що тест червоніє — доказ якості не в тому, що тест проходить, а в тому, що він падає, коли має.
Верифікація фактів
- Розумію, що конкретні факти (версії, заголовки, , сигнатури) — те, що модель галюцинує найохочіше.
- Можу пояснити, чому факт звіряють із першоджерелом (доки, спека, код застосунку), а не з тією ж моделлю.
- Знаю, чому «перепитав модель — вона підтвердила» не є верифікацією: те саме джерело, той самий bias, нуль нової інформації.
DoD і відповідальність
- Можу перелічити DoD для AI-коду (той самий, що й для власного, не м'якший): компілюється без вигаданих API, селектори резолвляться, тест червоніє на поломці, покрито негативні й граничні кейси, факти звірені, немає протікання секретів, стиль за конвенціями проєкту.
- Знаю, що (JSON за схемою) валідують програмно, а не очима.
- Можу чітко відповісти, хто відповідає за баг у AI-коді в проді: той, хто закомітив, а не модель.
- Тримаю в голові рамку «AI — швидкий джун, чию роботу апрувиш своїм підписом»: швидкість справжня, відповідальність за якість — на людині.
Квіз
Перед стартом
- Питань: 14
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Правдоподібність проти правильності — у чому різниця для AI-виводу?
