OpenAPI/Swagger: специфікація як джерело істини
Зміст
Уяви: тобі дали новий сервіс і сказали «протестуй API». Перше питання — де взяти правду про те, як цей API має працювати? Сторінка у вікі бреше, бо її писали пів року тому. Розробник відповідає «подивись у код». Postman-колекція колеги покриває третину ендпоінтів. Саме цю проблему розв'язує OpenAPI — формальний машиночитний опис API, який лежить у репозиторії поруч із кодом і слугує єдиним джерелом істини (source of truth) для документації, тестів, моків і клієнтського коду.
Ця глава недарма стоїть перед тест-дизайном для API: специфікація — це матеріал, з якого тест-дизайн робить кейси. Вміння читати її заощаджує години перепитувань: тест-кейси стають системними («покрив усе, що в контракті»), а не «що згадав — те й перевірив».
Що таке OpenAPI і до чого тут Swagger
OpenAPI Specification (OAS) — це стандарт опису HTTP API у форматі YAML або JSON. Один файл (часто openapi.yaml) перелічує всі ендпоінти, їхні параметри, формати запитів і відповідей, коди статусів і правила авторизації. Про сам REST і формати даних ми говорили в розділі про веб — специфікація просто записує все це у строгій, придатній для програмної обробки формі.
З назвами історична плутанина, яку люблять питати на співбесідах. Спочатку формат називався Swagger; 2015 року SmartBear передала специфікацію під крило OpenAPI Initiative (Linux Foundation), і з версії 3.0 стандарт зветься OpenAPI. Слово «Swagger» лишилося за екосистемою інструментів SmartBear: Swagger UI, Swagger Editor, Swagger Codegen. Тобто коректно так: OpenAPI — специфікація (стандарт), Swagger — інструменти навколо неї. У побуті «подивись у Swagger» майже завжди означає «відкрий Swagger UI з нашою специфікацією».
Навіщо опис саме машиночитний? Текст у вікі вміє читати тільки людина. А з формального YAML-файлу інструменти автоматично породжують цілу низку артефактів — і всі вони гарантовано узгоджені між собою, бо мають спільне джерело:
Для тестувальника з цього списку два пункти особливо смачні. По-перше, зі специфікації можна підняти mock-сервер, який віддаватиме відповіді за описаними схемами ще до того, як бекенд написаний, — механіку розбираємо в главі про мокання залежностей. По-друге, реальні відповіді API можна автоматично звіряти зі схемами — про це нижче.
Актуальна гілка стандарту — 3.x: найпоширеніші на практиці OpenAPI 3.0 (2017) і 3.1 (2021), у 2025-му вийшла версія 3.2. Стара версія Swagger 2.0 формально називається OpenAPI 2.0 і досі трапляється на легасі-проєктах.
Структура специфікації: paths, parameters, schemas, responses, $ref
Ось мінімальна, але цілком робоча специфікація одного ресурсу:
openapi: 3.0.3
info:
title: Users API
version: 1.0.0
paths:
/users/{id}:
get:
summary: Отримати користувача за id
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: Користувача знайдено
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: Користувача з таким id не існує
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: integer
email:
type: string
format: email
name:
type: string
maxLength: 50
Розберімо блоки, з якими працює QA:
paths — серце специфікації: перелік усіх шляхів і HTTP-методів на кожному. Пара «шлях × метод» називається операцією (operation). Якщо операції немає в paths — з погляду контракту її не існує.
parameters — усе, що приходить поза тілом запиту: параметри шляху (in: path, як {id} вище), query-параметри (in: query — фільтри, пагінація), заголовки (in: header). У кожного — тип, ознака required, іноді обмеження: enum з допустимими значеннями, minimum/maximum, pattern.
responses — задокументовані коди статусів і схема тіла для кожного. Це готовий перелік очікуваних результатів: якщо у специфікації для операції описані 200 і 404, обидва сценарії мають бути відтворювані (самі статус-коди — у розділі про веб).
components/schemas — словник моделей даних. Схема описує форму обʼєкта: які поля, яких типів, які обовʼязкові (required), які обмеження (format, maxLength, enum). Синтаксис схем — це діалект JSON Schema; в OpenAPI 3.1 сумісність із JSON Schema повна, у 3.0 є дрібні відмінності (наприклад, «поле може бути null» у 3.0 позначається окремим ключовим словом nullable: true). Детально про перевірки за JSON Schema — у главі про перевірки відповіді.
$ref — посилання на вже описаний фрагмент замість копіювання. Рядок $ref: '#/components/schemas/User' каже: «тут та сама схема User». Один опис моделі перевикористовується в десятках операцій, і зміна в одному місці міняє контракт скрізь. Для QA це підказка: якщо схему User поправили, зачеплені всі ендпоінти, які на неї посилаються, — ось і скоуп регресії.
Swagger UI: специфікація очима людини
Swagger UI — вебсторінка, яка рендерить специфікацію в інтерактивну документацію: список операцій, розгорнуті схеми, приклади запитів. На багатьох проєктах вона доступна прямо на тестовому стенді за адресою на кшталт /swagger або /api-docs.
Найкорисніша кнопка — Try it out: заповнюєш параметри, тиснеш Execute — і Swagger UI надсилає справжній запит на середовище й показує справжню відповідь. Це найшвидший спосіб помацати незнайомий API без Postman і curl. Два застереження. Перше: запити справжні — DELETE через Try it out на спільному стенді видалить дані так само, як будь-який інший клієнт. Друге: те, що показує UI (схеми, приклади), — це вміст специфікації, а не поведінка сервера; правдивість цих обіцянок ще треба перевірити.
Swagger UI зручний для розвідки й разових перевірок; системну роботу з запитами краще вести в Postman або в коді.
Від специфікації до тест-кейсів
Головна практична навичка цієї глави: специфікація — це готовий каркас тест-аналізу. Кожен блок відповідає на своє тестове питання:
| Блок | Що там записано | Які тести з цього народжуються |
|---|---|---|
paths | усі операції API | мапа покриття: кожна пара «шлях × метод» викликана хоча б раз |
parameters | типи, required, enum, межі | позитив із валідними значеннями; негатив: пропущений обовʼязковий, чужий тип, значення поза enum |
requestBody + schemas | обовʼязкові поля, формати, довжини | межові значення, відсутні required-поля, невалідні формати |
responses | задокументовані статуси | окремий сценарій на кожен статус: як добитися 200, 404, 422 |
securitySchemes | схеми авторизації | запит без токена і з невалідним токеном (докладно — у главі про авторизацію в API) |
Подивись ще раз на схему User вище — вона сама диктує перевірки. email має format: email — отже, потрібен негативний тест із рядком без «собачки». name має maxLength: 50 — класична пара межових значень: 50 символів проходить, 51 — ні. id та email у required — а name ні, отже, відповідь без name валідна, і клієнт зобовʼязаний таке переживати. Кожне обмеження у схемі — це межа, а межі — улюблене місце проживання багів. Як системно застосовувати техніки еквівалентності й межових значень до параметрів — у главі про тест-дизайн для API; самі техніки — в розділі про тест-дизайн.
Бонус такого підходу — вимірність. «Я протестував API» — твердження ні про що; «покрито всі операції з paths, для кожної — всі задокументовані статуси плюс негативи за обмеженнями схем» — уже осмислена заявка на покриття, яку можна показати команді.
Коли дока бреше: розбіжності специфікації й реальності
Специфікація — джерело істини за задумом, але не за фактом: вона теж написана людьми і теж відстає від коду. Типові розбіжності, які варто шукати свідомо:
- у реальній відповіді є поля, яких немає у схемі (або навпаки — обіцяне поле не приходить);
- сервер повертає статус, не описаний у
responses: у доці400, у житті500; - тип не збігається: у схемі
id: integer, а приходить рядок"42"; - поле в
required, а насправді буваєnullчи зникає; - параметр позначений
required: true, але запит без нього чомусь проходить.
Кожна така знахідка — дефект. Питання лише, чий: коду чи специфікації. Відповідь залежить від того, як команда працює з контрактом. У підході design-first специфікацію пишуть до коду, вона і є узгодженим контрактом — тоді розбіжність майже завжди баг реалізації. У підході code-first специфікація генерується з анотацій у коді — тоді вона автоматично встигає за кодом, але сумлінно документує і його помилки, а «істина» розмивається. Тестувальнику не треба вирішувати цю суперечку самотужки — треба зафіксувати розбіжність, показати обидві сторони (що обіцяно і що прийшло) і винести на команду. Мовчки підлаштувати тест під фактичну поведінку — найгірший варіант: контракт продовжить брехати наступному, хто його прочитає.
Розбіжність, яку зловили не тести, а інша команда у своєму продакшн-коді, називається дрейфом схеми (schema drift) — і це вже територія контрактного тестування.
Валідація відповідей проти схеми
Очима дрейф не ловиться: поле, що зникло з відповіді, у JSON на пів екрана просто не помітиш. Тому звірку відповідей зі схемами автоматизують — кожен автотест, окрім своїх функціональних перевірок, проганяє тіло відповіді через валідатор схем. У JavaScript/TypeScript стандартний інструмент — Ajv, валідатор JSON Schema:
import { test, expect } from '@playwright/test';
import Ajv from 'ajv';
// Схема — з components/schemas специфікації
const userSchema = {
type: 'object',
required: ['id', 'email'],
properties: {
id: { type: 'integer' },
email: { type: 'string' },
name: { type: 'string', maxLength: 50 },
},
additionalProperties: false,
};
test('GET /users/1 відповідає схемі User', async ({ request }) => {
const response = await request.get('/api/users/1');
expect(response.status()).toBe(200);
const ajv = new Ajv();
const valid = ajv.validate(userSchema, await response.json());
expect(valid, JSON.stringify(ajv.errors)).toBe(true);
});
Зверни увагу на additionalProperties: false — без нього зайві, не описані у схемі поля пройшли б валідацію мовчки, бо за замовчуванням JSON Schema додаткові поля дозволяє. Саме так непомічені поля з персональними даними випливають у публічних відповідях.
І тверезе застереження: схема перевіряє форму, а не зміст. Відповідь, де email — синтаксично валідна адреса іншого користувача, схему пройде бездоганно. Валідація проти схеми — це страхувальна сітка від дрейфу контракту, а не заміна функціональних перевірок. Як вбудувати її в структуру автотестів — у главі про API-автотести в коді.
Типові помилки
- Виглядає як «Swagger — це наша документація», а насправді Swagger UI — лише рендер файлу специфікації. Джерело — YAML/JSON у репозиторії; якщо на стенд задеплоєна стара версія, красива сторінка впевнено бреше.
- Виглядає як «у специфікації описаний
200— значить, ендпоінт працює», а насправді специфікація описує намір, а не факт. Це список обіцянок для перевірки, а не результат перевірки. - Виглядає як «поле не в
required, тестувати нічого», а насправді опціональність — це окрема гілка поведінки: відповідь без цього поля валідна, і все, що її споживає, мусить це переживати. Опціональні поля — класичне гніздо null-багів. - Виглядає як «схема пройшла — відповідь правильна», а насправді схема перевіряє типи і структуру, а не бізнес-логіку. Чужі дані правильної форми валідатор пропустить.
- Виглядає як «додаткових полів у відповіді не буде, у схемі ж їх немає», а насправді без явного
additionalProperties: falseзайві поля дозволені й валідацію проходять. - Виглядає як «дока застаріла, орієнтуюсь на фактичну поведінку», а насправді так контракт остаточно втрачає сенс. Розбіжність — це дефект, який треба зафіксувати, а не обійти.
Підсумок
- Специфікація OpenAPI — машиночитний контракт API і єдине джерело істини; Swagger UI — лише її людиночитний рендер.
- Кожен блок специфікації — готовий чек-лист:
pathsдає мапу покриття,parametersіschemas— межі й негативи,responses— перелік статусів, кожен з яких треба вміти відтворити. - Розбіжність специфікації й реальності — завжди дефект; чий саме (коду чи доки) — вирішує команда залежно від підходу design-first чи code-first, але фіксувати її — робота QA.
- Валідація відповіді проти схеми ловить дрейф контракту, який не видно очима, — але перевіряє форму, а не зміст.
$refзвʼязує схеми в граф: зміна однієї моделі зачіпає всі операції, що на неї посилаються, — це готовий скоуп регресії.
Що питають на співбесіді
- «Чим OpenAPI відрізняється від Swagger?» — перевіряють охайність термінології: OpenAPI — стандарт специфікації, Swagger — екосистема інструментів навколо нього (UI, Editor, Codegen). Плутанина пробачна, але точна відповідь одразу додає балів.
- «Як ти використовуєш специфікацію при тестуванні API?» — чекають системності, а не «дивлюсь у Swagger, що там є»: покриття з
paths, негативи з обмежень параметрів і схем, сценарій на кожен задокументований статус. - «Реальна відповідь не збігається з докою. Твої дії?» — дивляться на процесне мислення: зафіксувати обидві сторони розбіжності, зʼясувати, що в команді є джерелом істини, завести дефект — а не мовчки підігнати тест під факт.
- «Як перевірити, що відповідь відповідає схемі?» — хочуть почути про автоматичну валідацію (JSON Schema, Ajv чи аналог) і про її межу: форма — так, бізнес-логіка — ні.
- «Навіщо машиночитна специфікація, якщо є сторінка у вікі?» — перевіряють розуміння ідеї: з формального опису генеруються узгоджені між собою дока, моки, типи й валідація; вікі ж дрейфує мовчки і нічого не породжує.
Джерела
- OpenAPI Specification — актуальний текст стандарту.
- Learn OpenAPI — офіційний навчальний ресурс OpenAPI Initiative: структура специфікації з прикладами.
- Swagger UI — офіційна сторінка й документація інструмента.
- JSON Schema — стандарт опису схем даних, на якому побудовані схеми OpenAPI.
- Ajv — документація валідатора JSON Schema для JavaScript/TypeScript.
Що таке OpenAPI і навіщо він потрібен тестувальнику?
OpenAPI Specification (OAS) — це формальний машиночитний опис HTTP API у форматі YAML або JSON: у ньому зібрані всі операції, їхні параметри, схеми запитів і відповідей, статус-коди й правила авторизації. Файл живе в репозиторії разом із кодом і виконує роль єдиного джерела істини (source of truth). Для QA це не «ще одна дока», а готовий каркас тест-аналізу: контракт показує повний обсяг операцій та обмежень, тож покриття будується від нього, а не від памʼяті — «перевірив усе, що обіцяно», замість «перевірив те, що згадалось». Плюс зі специфікації інструменти автоматично піднімають mock-сервер і валідують реальні відповіді проти схем. Практичний наслідок: замість перепитувань у розробника «а що цей ендпоінт має повертати» ти читаєш контракт і одразу бачиш скоуп перевірок.
Чим OpenAPI відрізняється від Swagger?
OpenAPI — це стандарт (сама специфікація), а Swagger — екосистема інструментів навколо нього: Swagger UI, Swagger Editor, Swagger Codegen. Плутанина історична: формат народився під іменем Swagger, у 2015-му SmartBear віддала специфікацію OpenAPI Initiative під Linux Foundation, і починаючи з 3.0 стандарт носить імʼя OpenAPI, а бренд Swagger лишився на інструментах SmartBear. Тож коли колега каже «глянь у Swagger», ідеться зазвичай про Swagger UI, який рендерить вашу специфікацію. На співбесіді цим питанням перевіряють охайність термінології — точна відповідь одразу додає балів.
Яка зараз актуальна версія стандарту і що не так зі старим Swagger 2.0?
Актуальна гілка — 3.x: у роботі найчастіше зустрінеш OpenAPI 3.0 (2017) і 3.1 (2021); восени 2025-го опубліковано 3.2. Swagger 2.0 — та сама специфікація до перейменування (офіційно OpenAPI 2.0), і на легасі-проєктах вона досі жива. Знати номер версії корисно не для педантизму: між 3.0 і 3.1 є практичні відмінності, які впливають на читання схем. Наприклад, у 3.0 «поле може бути null» позначається окремим ключовим словом nullable: true, а в 3.1 сумісність із JSON Schema повна, і null описується інакше. Тому перше, на що варто глянути у файлі, — рядок openapi: з версією.
З яких основних блоків складається специфікація?
Ключові блоки, з якими працює QA, — paths, parameters, responses, components/schemas і посилання $ref. paths — центральний блок: у ньому всі шляхи API і HTTP-методи на кожному з них. parameters описує все, що приходить поза тілом запиту: параметри шляху, query-параметри, заголовки. responses перелічує задокументовані коди статусів і схему тіла для кожного. components/schemas — словник моделей даних, а $ref дозволяє посилатися на вже описаний фрагмент замість копіювання. Верхньорівнево є ще блок info (назва, версія API), а схеми авторизації описують у components/securitySchemes. Читаючи незнайому специфікацію, зазвичай ідуть згори вниз: спочатку paths — щоб побачити мапу API, потім у кожну операцію — за параметрами, тілом і відповідями.
Що таке операція (operation) у термінах OpenAPI?
Операція — це пара «шлях × метод»: наприклад, GET /users/{id} і DELETE /users/{id} — це дві різні операції на одному шляху. У специфікації вони живуть у блоці paths: під кожним шляхом перелічені HTTP-методи, а під кожним методом — його параметри, тіло запиту й відповіді. Ключова думка для тестування: чого немає в paths — того для контракту не існує. Тому paths дає готову мапу покриття — кожна пара «шлях × метод» має бути викликана хоча б раз. Коли команді треба показати покриття API, рахують саме операції, а не абстрактні «фічі».
Що описує блок parameters і які бувають типи параметрів?
parameters описує все, що приходить у запит поза його тілом, і в OpenAPI кожен параметр має поле in, яке каже, звідки він береться. Основні значення: in: path — сегменти шляху на кшталт {id} у /users/{id}, in: query — усе після знака питання в URL (фільтри, сторінки пагінації), in: header — заголовки запиту. У кожного параметра є тип, ознака required і часто обмеження: enum із допустимими значеннями, minimum/maximum, pattern. Для QA це прямий генератор тестів: на кожен параметр — позитив із валідним значенням і негативи (пропущений обовʼязковий, чужий тип, значення поза enum, вихід за межі). Обмеження в описі параметра — це фактично готовий список меж, які треба перевірити.
Навіщо у специфікації responses, якщо є код застосунку?
responses — це задокументований перелік очікуваних результатів операції: які коди статусів вона може повернути і яка схема тіла в кожного. Це важливо саме тому, що описує намір, а не факт: якщо для операції задокументовані 200, 404 і 422, то кожен із цих сценаріїв має бути відтворюваним, і завдання QA — знайти вхідні дані, які дадуть кожен код. Тобто responses перетворюється на список тест-сценаріїв: як добитися 200, як спровокувати 404, як отримати 422. Дзеркальний бік: якщо API повертає код, якого в responses немає (у доці 400, у житті 500) — це вже знахідка. Код застосунку відповість на питання «як воно поводиться», а специфікація — «як воно має поводитися», і різниця між ними і є дефект.
Що таке components/schemas і як вони пов'язані з JSON Schema?
components/schemas — це словник моделей даних: кожна схема описує форму обʼєкта (які поля, яких типів, які обовʼязкові, які обмеження). Синтаксис схем — це діалект JSON Schema, стандарту опису структури даних. В OpenAPI 3.1 сумісність із JSON Schema повна, а в 3.0 є дрібні відмінності — найпомітніша та сама історія з nullable: true замість звичного для JSON Schema способу описати null. Для QA схема — це готовий набір перевірок: поле в required має бути присутнім, format: email вимагає негативу з невалідною адресою, maxLength: 50 дає пару межових значень. Ба більше, ту саму схему можна згодувати валідатору (наприклад, Ajv) і автоматично звіряти з нею реальні відповіді.
Що робить $ref і чому він важливий для оцінки скоупу регресії?
$ref — це посилання на вже описаний фрагмент замість його копіювання: рядок $ref: '#/components/schemas/User' каже «тут та сама схема User, дивись у components». Сенс — не дублювати опис моделі: одну модель підключають у десятках операцій, тож правка в одному місці розходиться по всьому контракту. Саме тому $ref — важливий сигнал для оцінки регресії: після правки схеми User під перевірку потрапляє кожен ендпоінт, що на неї посилається, — це і є скоуп перетестування. По суті $ref звʼязує схеми в граф залежностей, і читання цього графа економить час: не треба вгадувати, що ще могло зламатися. Тому при зміні спільної моделі перше питання QA — «хто на неї посилається».
Що таке Swagger UI і чим корисна (та небезпечна) кнопка Try it out?
Swagger UI — це вебінтерфейс поверх файлу специфікації: він перетворює YAML/JSON на інтерактивну сторінку з переліком операцій, схемами й прикладами; на стендах його зазвичай шукають за шляхами типу /swagger чи /api-docs. Найцінніше в ньому — Try it out: вводиш параметри, натискаєш Execute — і UI шле реальний запит на стенд та показує живу відповідь; це найшвидший спосіб познайомитися з невідомим API, не відкриваючи Postman чи curl. Але є два застереження. Перше: запити не іграшкові — деструктивний DELETE зі спільного стенда зітре дані так само, як з будь-якого іншого клієнта. Друге: схеми і приклади на сторінці беруться з файлу специфікації, а не з поведінки сервера, тож їхню правдивість ще належить перевірити. Тобто Swagger UI чудовий для розвідки й разових перевірок, але системну роботу краще вести в Postman або в коді.
Як зі специфікації системно народжуються тест-кейси?
Кожен блок специфікації відповідає на своє тестове питання, тож специфікація читається як готовий чек-лист. paths дає мапу покриття: кожна операція має бути викликана хоча б раз. parameters дає позитиви (валідні значення) і негативи (пропущений обовʼязковий, чужий тип, значення поза enum, вихід за межі). requestBody разом зі схемами дає межові значення, відсутні обовʼязкові поля й невалідні формати. responses дає окремий сценарій на кожен задокументований статус. securitySchemes дає запит без токена і з невалідним токеном. Бонус такого підходу — вимірність: «кожна операція з paths викликана, кожен задокументований статус відтворений, негативи за обмеженнями схем пройдені» — таку заявку на покриття не соромно показати команді, на відміну від безпредметного «я протестував API».
Схема User має email з format:email і name з maxLength:50. Які тести це диктує?
Схема сама диктує перевірки, і кожне обмеження — це межа, де живуть баги. email із format: email вимагає негативного тесту з рядком без «собачки» — валідатор має його відхилити. name із maxLength: 50 — підручникова пара меж: рядок із 50 символів сервер приймає, з 51 — відхиляє. Якщо id та email стоять у required, а name ні — контракт дозволяє відповідь без name, і споживач мусить бути до цього готовий, тож потрібен і тест на присутність обовʼязкових полів, і сценарій із відсутнім опціональним. Тобто з трьох рядків схеми вже вимальовується позитив, негатив на формат, пара межових значень і перевірка обовʼязковості. Саме тому читати схему вигідно до написання тестів — вона економить вигадування кейсів.
Поле не в required. Це означає, що тестувати там нічого?
Навпаки — опціональність це окрема гілка поведінки, яку треба перевірити свідомо. Якщо поле не в required, то відповідь без нього валідна за контрактом, а отже, весь код, який це поле споживає, мусить коректно переживати його відсутність. Опціональні поля — класичний розсадник null-багів: фронтенд чи інший сервіс очікує рядок, а отримує undefined/null і падає. Тому на опціональне поле варто мати як мінімум два сценарії: коли воно є і коли його немає. Помилка «поле не обовʼязкове, тестувати нічого» — типова пастка джуна; сильний кандидат бачить тут окрему поведінку, а не порожнє місце.
Навіщо машиночитна специфікація, якщо вже є сторінка у вікі?
Тому що вікі вміє читати тільки людина, а машиночитний файл породжує узгоджені між собою артефакти. З одного YAML/JSON інструменти автоматично генерують Swagger UI (документацію), mock-сервер (відповіді за схемами ще до готового бекенда), клієнтський код і типи, а також валідацію відповідей у тестах — і всі вони гарантовано узгоджені, бо мають спільне джерело. Вікі ж нічого не породжує і дрейфує мовчки: її написали пів року тому, і ніщо не сигналить, що вона розійшлася з реальністю. Специфікація теж може відставати, але вона хоча б формальна: розбіжність зі схемою можна зловити автоматично. На співбесіді цим питанням перевіряють, чи розумієш ти саму ідею source of truth, а не просто «дока в новому форматі».
Реальна відповідь API не збігається зі специфікацією. Твої дії?
Це процесне питання, і від тебе чекають не «підлаштую тест», а дисципліни фіксації. Спочатку зафіксувати обидві сторони розбіжності: що обіцяно у специфікації і що реально прийшло (код, поле, тип). Потім зʼясувати, що в команді вважається джерелом істини — специфікація чи код, бо від цього залежить, чий це баг. Далі завести дефект із доказами обох сторін і винести на команду. Найгірший варіант — мовчки переписати асерт під факт: тоді специфікація й далі обіцятиме неправду кожному наступному читачеві, і сенс контракту втрачається. Тобто розбіжність — це завжди дефект, питання лише чий (коду чи доки), і розвʼязувати цю суперечку тестувальник не мусить самотужки — його робота зафіксувати й показати.
Чим відрізняються підходи design-first і code-first і як це впливає на те, чий баг розбіжність?
У підході design-first специфікацію пишуть до коду — вона і є узгодженим контрактом, за яким потім реалізують API. Тоді розбіжність між специфікацією і реальністю майже завжди баг реалізації: код не дотягнув до контракту. У підході code-first специфікацію породжують з анотацій просто в коді: відставати вона не може, зате чесно фіксує й помилки реалізації, тож «істина» розмивається — файл описує те, що є, а не те, що мало б бути. Практичний наслідок для QA: знаючи підхід команди, ти одразу розумієш, куди дивитися. При design-first довіряєш специфікації як контракту й заводиш баг на код; при code-first специфікація — слабший аргумент, і розбіжність частіше означає, що треба уточнювати очікувану поведінку. Але в обох випадках фіксувати розбіжність — робота тестувальника.
Що таке дрейф схеми (schema drift)?
Дрейф схеми — це поступове розходження між задокументованою схемою і реальними відповідями API, яке накопичується непомітно: додали поле в код і забули в специфікацію, змінили тип, прибрали поле з відповіді. Особливо підступно те, що вручну дрейф не ловиться: зникле поле серед десятків рядків JSON око просто не вихоплює. Класичний сценарій — коли дрейф ловлять не тести, а інша команда у своєму продакшн-коді: вона покладалася на поле, якого вже немає. Саме тому звірку відповідей зі схемами автоматизують, а системна боротьба з дрейфом між командами — це вже територія контрактного тестування. Для QA дрейф — головний аргумент на користь автоматичної валідації проти схеми в кожному тесті.
Як автоматично перевірити, що відповідь API відповідає схемі?
Реальну відповідь проганяють через валідатор JSON Schema, і в JavaScript/TypeScript стандартний інструмент — Ajv. Схему беруть із components/schemas специфікації, а в тесті, окрім звичних функціональних перевірок, віддають тіло відповіді валідатору й асертять, що воно валідне. У Playwright це виглядає так: отримав відповідь, ajv.validate(userSchema, await response.json()), і expect(valid).toBe(true), а в повідомлення асерту зручно покласти ajv.errors, щоб одразу бачити, яке саме поле не збіглося. Сенс у тому, що така перевірка ловить дрейф контракту, який не видно очима, — зниклі поля, змінені типи, порушені формати. Це страхувальна сітка, яку додають до кожного API-тесту майже даром, бо схема вже описана у специфікації.
Навіщо у схемі валідації потрібен additionalProperties: false?
Тому що дефолтна поведінка JSON Schema — пропускати поля, яких у схемі немає, без жодного попередження. additionalProperties: false вмикає сувору перевірку — будь-яке поле, якого немає в схемі, робить відповідь невалідною. Для QA це критично: саме через відсутність цієї опції у відповідях непомітно випливають зайві поля, зокрема з персональними даними, які туди потрапили випадково. Класичний баг безпеки — коли ендпоінт починає віддавати внутрішнє поле (хеш пароля, службовий прапорець), а тест цього не ловить, бо схема без additionalProperties: false на зайве поле не реагує. Тобто ця опція перетворює валідацію зі «все обовʼязкове на місці?» на «і нічого зайвого немає?». Ставити її треба свідомо там, де контракт справді закритий.
Схема пройшла — чи означає це, що відповідь правильна?
Ні: схема перевіряє форму, а не зміст. Якщо API повернув дані іншого користувача, але email там синтаксично коректний, валідатор не заперечить: тип правильний, формат правильний, поле присутнє — а дані чужі. Схемна валідація страхує від дрейфу контракту (зниклі поля, змінені типи), але функціональних перевірок бізнес-логіки не замінює. Тому в тесті поруч зі схемною валідацією мають жити перевірки конкретних значень: що повернувся саме той користувач, якого запитали, з очікуваними даними. Плутати ці два рівні небезпечно: «схема зелена» заспокоює, але не гарантує, що API повернув правильну відповідь по суті. Сильний кандидат на співбесіді цю межу проговорює одразу.
Три кейси, які проходить тестувальник із живою специфікацією: перетворити фрагмент OpenAPI на матрицю тест-кейсів, автоматично звірити реальну відповідь зі схемою через Ajv (і зловити зайве поле) та коректно оформити розбіжність між докою й реальністю. Скрізь — що дивитися і чому.
Кейс 1. Від фрагмента специфікації до матриці тест-кейсів
Тобі дали новий ендпоінт і фрагмент специфікації. Замість «повикликаю та подивлюсь» читаємо контракт і виводимо перевірки по блоках.
paths:
/users:
post:
summary: Створити користувача
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/NewUser'
responses:
'201': { description: Створено }
'409': { description: email уже зайнятий }
'422': { description: Не пройшло валідацію }
components:
schemas:
NewUser:
type: object
required: [email, role]
properties:
email: { type: string, format: email }
role: { type: string, enum: [admin, user, guest] }
name: { type: string, maxLength: 50 }
Кожен рядок схеми — це готовий рядок тест-плану. required: [email, role], format: email, enum на role, maxLength: 50 на name і три задокументовані статуси перетворюються на матрицю:
| Джерело у специфікації | Тест-кейс | Очікування |
|---|---|---|
responses: 201 | валідне тіло з email + role | 201, у відповіді новий ресурс |
required: [email] | тіло без email | 422 |
required: [role] | тіло без role | 422 |
format: email | email без «собачки» ("petro.example.com") | 422 |
enum на role | role: "superadmin" (поза списком) | 422 |
maxLength: 50 | name рівно 50 символів | 201 |
maxLength: 50 | name 51 символ | 422 |
responses: 409 | email, який уже існує | 409 |
опціональність name | тіло без name | 201 (поле не обовʼязкове) |
Що дивитися і чому:
requiredіenumдають негативи майже даром. Не треба вигадувати кейси — специфікація сама каже, де межа: пропущене обовʼязкове поле, значення позаenum, перевищена довжина.maxLength: 50— це пара межових значень, а не одне. 50 проходить, 51 — ні; перевіряти треба обидва боки межі, бо баги живуть саме на переході.- Опціональне
name— окремий рядок плану. Тіло без нього має дати201; це перевірка, що API справді вважає поле необовʼязковим, а не «забули згадати в required». - Кожен статус із
responses— окремий сценарій.409не зʼявиться сам — його треба спровокувати дублемemail; якщо відтворити не вдається, це вже питання до контракту.
Кейс 2. Playwright + Ajv: звірити відповідь зі схемою і зловити зайве поле
Функціональний асерт перевіряє значення, схемна валідація — форму. Разом вони ловлять і логічні баги, і дрейф контракту. Схему беремо з components/schemas, а additionalProperties: false додаємо свідомо, щоб зловити поля, яких у контракті бути не має.
import { test, expect } from '@playwright/test';
import Ajv from 'ajv';
const userSchema = {
type: 'object',
required: ['id', 'email'],
properties: {
id: { type: 'integer' },
email: { type: 'string' },
name: { type: 'string', maxLength: 50 },
},
additionalProperties: false, // жодних не описаних у контракті полів
};
test('GET /users/1 — і форма, і зміст', async ({ request }) => {
const res = await request.get('/api/users/1');
expect(res.status()).toBe(200);
const body = await res.json();
// 1) форма: відповідь відповідає схемі
const ajv = new Ajv();
const valid = ajv.validate(userSchema, body);
expect(valid, JSON.stringify(ajv.errors)).toBe(true);
// 2) зміст: повернувся саме той користувач (схема цього не перевіряє)
expect(body.id).toBe(1);
});
Уяви, що бекенд випадково почав віддавати службове поле passwordHash. Функціональний асерт body.id === 1 цього не помітить — id правильний. А Ajv через additionalProperties: false впаде з ajv.errors, де прямо вказано зайве поле:
[{ "keyword": "additionalProperties",
"params": { "additionalProperty": "passwordHash" },
"message": "must NOT have additional properties" }]
Що дивитися і чому:
- Без
additionalProperties: falseвитік поля пройшов би мовчки. За замовчуванням JSON Schema дозволяє додаткові поля; саме так персональні чи службові дані непомітно випливають у публічні відповіді. ajv.errorsу повідомленні асерту — це діагноз замість «щось не так». Одразу видно, яке поле зайве або який тип не збігся, без ручного порівняння JSON.- Схема і функціональний асерт перевіряють різне. Прибереш
expect(body.id).toBe(1)— і чужий користувач правильної форми пройде; прибереш Ajv — і дрейф контракту (зниклий/зайвий/перейменований ключ) залишиться непоміченим. - Не вішай
additionalProperties: falseнаосліп на все. На відкритих, розширюваних контрактах зайве поле може бути легітимним; сувору перевірку ставлять там, де контракт справді закритий.
Кейс 3. Дока бреше: як оформити розбіжність
Тест на POST /users із дубльованим email очікує 409 (так у responses), а сервер повертає 500 зі стектрейсом. Спокуса — переписати асерт на 500 і йти далі. Це найгірший варіант: контракт продовжить брехати наступному.
Правильний хід — зафіксувати обидві сторони й винести на команду:
Ендпоінт: POST /users
Сценарій: email, який уже існує в базі
Очікувано: 409 (responses у openapi.yaml)
Фактично: 500 Internal Server Error, unhandled exception
Стектрейс: UniqueConstraintError у UsersController#create
Далі рішення залежить від того, як команда працює з контрактом:
| Підхід | Що є джерелом істини | Куди дефект |
|---|---|---|
| design-first | специфікація — узгоджений контракт до коду | баг реалізації: код має віддавати 409, а не падати |
| code-first | специфікація генерується з коду | усе одно баг: 500 на дубль — пропущена обробка, але й контракт варто оновити |
Що дивитися і чому:
500на передбачуваний конфлікт — щонайменше півбаг бекенда. Добре спроєктований сервер відповідає свідомим409/422, а не падає з необробленим винятком; тригер (дубльemail) прийшов від клієнта, але реакція сервера — окремий дефект.- Фіксуй обидві сторони, а не одну. «Очікувано
409за специфікацією, фактично500зі стектрейсом» — це доказ; «ендпоінт падає» — ні. - Не вирішуй суперечку контракту самотужки. Твоя робота — показати, що обіцяно і що прийшло; чиє це джерело істини (специфікація чи код) — рішення команди, але без твоєї фіксації розбіжність просто тихо доживе до продакшену.
- Мовчки підігнати тест під факт = знищити контракт. Асерт на
500зробить тест зеленим, але узаконить баг і залишить наступного читача специфікації з брехливою обіцянкою409.
OpenAPI vs Swagger і навіщо специфікація
- Можу розвести терміни: OpenAPI — стандарт (сама специфікація), Swagger — екосистема інструментів (UI, Editor, Codegen); формат починався як Swagger, 2015 року перейшов до OpenAPI Initiative, з версії 3.0 зветься OpenAPI.
- Розумію, що специфікація — машиночитний контракт у YAML/JSON у репозиторії як єдине джерело істини (source of truth); з неї генеруються Swagger UI, mock-сервер, клієнти й типи, валідація — усі узгоджені, бо джерело спільне.
- Знаю актуальні версії: 3.0 (2017), 3.1 (2021), 3.2 (2025); Swagger 2.0 = OpenAPI 2.0 — легасі.
Структура специфікації
- Розумію, що операція (operation) — це пара «шлях × метод»; якщо її немає в
paths, з погляду контракту її не існує. - Знаю, що
parametersописує все поза тілом запиту через полеin(in: path,in: query,in: header), а обмеження, що диктують негативи, —required,enum,minimum/maximum,pattern,format,maxLength. - Розумію, що
responses— задокументовані коди статусів зі схемою тіла, тобто список сценаріїв, кожен з яких треба вміти відтворити. - Знаю, що
components/schemas— словник моделей на діалекті JSON Schema; у 3.1 сумісність повна, у 3.0 null позначається окремимnullable: true. - Можу пояснити
$ref: посилання замість копіювання; зміна однієї схеми зачіпає всі операції, що на неї посилаються, — готовий скоуп регресії.
Swagger UI
- Розумію, що Swagger UI — лише рендер файлу специфікації, часто доступний на стенді за
/swaggerабо/api-docs; системну роботу краще вести в Postman або в коді. - Знаю про кнопку Try it out і два застереження: запити справжні (
DELETEреально видаляє), а показані схеми — це вміст специфікації, а не поведінка сервера.
Від специфікації до тест-кейсів
- Можу побудувати мапу покриття з
paths: кожна операція викликана хоча б раз. - Виводжу негативи з обмежень: пропущений обовʼязковий параметр, чужий тип, значення поза
enum, вихід за межі, неваліднийformat. - Роблю окремий сценарій на кожен задокументований статус у
responses(200,404,422), а зsecuritySchemes— запит без токена і з невалідним. - Розумію, що опціональне поле (не в
required) — це окрема гілка поведінки й гніздо null-багів; покриття формулюю вимірно («усі операції + усі статуси + негативи за обмеженнями»), а не «я протестував API».
Коли дока розходиться з реальністю
- Знаю типові розбіжності: зайве/зникле поле, недокументований статус, розбіжність типу,
required-поле буваєnull,required: trueне спрацьовує — будь-яка це дефект, питання лише чий. - Можу пояснити різницю design-first (специфікація — контракт, розбіжність = баг коду) і code-first (специфікація генерується з коду й документує його ж помилки).
- Знаю правильний процес: зафіксувати обидві сторони, зʼясувати джерело істини в команді, завести дефект — а не мовчки підганяти тест під факт.
- Розумію, що таке дрейф схеми (schema drift) і чому очима він не ловиться.
Валідація відповідей проти схеми
- Можу пояснити, навіщо кожен автотест проганяє тіло відповіді через валідатор JSON Schema (в JS/TS — Ajv), і навіщо
additionalProperties: false: без нього зайві поля (зокрема з персональними даними) проходять валідацію мовчки. - Розумію межу валідації: вона перевіряє форму (типи, структуру, обовʼязковість), а не зміст — чужі дані правильної форми пройдуть; тому вона доповнює, а не замінює функціональні перевірки значень.
Як коректно розвести терміни OpenAPI і Swagger?
Питання
OpenAPI vs Swagger — у чому різниця?