vyvchy
    Теми розділу

    04 · API-тестування

    Postman: скрипти, data-driven, Newman

    Зміст

    Попередня глава навчила збирати запити, тримати змінні й додавати перевірки зі сніпетів. Але доти запити були поодинокими: надіслав — глянув очима — забув. Реальні API-сценарії влаштовані інакше: це ланцюжки. Залогінься → отримай токен → створи замовлення → перевір, що воно зʼявилось зі статусом pending. Кожен наступний крок залежить від відповіді попереднього. А ще той самий сценарій хочеться прогнати не раз, а на десятку наборів даних — валідні, невалідні, крайові. І бажано, щоб усе це крутилось у CI без людини за кермом. Для цих трьох задач у Postman є три інструменти: скрипти, data-driven прогін і Newman. Про них ця глава.

    Якщо ти при першому проході ще не працюєш з автоматизованими прогонами, цю главу можна відкласти й повернутися перед співбесідою — про Newman і ланцюжки питають часто, особливо коли у вакансії стоїть «Postman + CI». Разом з тим це канонічний виклад теми на сайті: інші глави, торкаючись скриптів чи Newman, посилаються сюди. Базу — колекції, рівні змінних, спадкування авторизації — вважаємо пройденою в главі «Postman: основи».

    Скрипти: pre-request і post-response

    Postman дозволяє вставити JavaScript у дві точки життєвого циклу кожного запиту:

    • pre-request script (пре-реквест скрипт) — виконується до надсилання. Тут готують запит: обчислюють підпис, генерують унікальний email, підставляють поточний timestamp, дописують заголовок.
    • post-response script (пост-респонс скрипт; у старих версіях вкладка називалась Tests) — виконується після отримання відповіді. Тут перевіряють відповідь і витягують із неї дані для наступного кроку.

    Важлива межа, яку часто недооцінюють: скрипти виконуються не в повноцінному Node.js, а в пісочниці (sandbox) — обмеженому середовищі з фіксованим набором вбудованих бібліотек. Асерти дає Chai, є Lodash, доступні деякі утиліти для криптографії й роботи з датами. Але звернутися до файлової системи чи поставити пакет так, як у проєкті на диску, не вийде — це керований рантайм, а не тестовий фреймворк (новіші версії Postman уміють підтягувати публічні пакети з npm через власний механізм пакетів, але в Newman він не працює). Уся взаємодія з Postman іде через глобальний обʼєкт pm (звідси pm.test, pm.response, pm.environment — усе, що ми бачили в главі про основи).

    Скрипти можна писати на трьох рівнях — колекції, папки й запиту, — і вони виконуються від ширшого до вужчого: спершу pre-request колекції, потім папки, потім самого запиту; після відповіді — так само post-response колекції → папки → запиту. Практичний наслідок: спільну підготовку (наприклад, оновлення токена перед кожним запитом) кладуть на рівень колекції один раз, а не копіюють у кожен запит.

    Ланцюжки запитів через змінні

    Ось де скрипти показують справжню цінність. Відповідь одного запиту майже завжди містить дані, потрібні наступному: створив користувача — сервер повернув його id, і цей id треба підставити в URL наступного запиту GET /users/{id}. Захардкодити його не можна — на кожному прогоні він новий. Розвʼязання: у post-response скрипті витягти значення з відповіді й записати у змінну, а наступний запит читає цю змінну через {{...}}.

    // post-response запиту Login
    pm.test("Логін віддав токен", () => {
        const body = pm.response.json();
        pm.expect(body.token).to.be.a("string");
        pm.collectionVariables.set("token", body.token);
    });

    Тепер будь-який наступний запит використовує Bearer {{token}} в авторизації — і токен «підʼїжджає» сам.

    Запит Get ordersЗмінна tokenЗапит LoginЗапит Get ordersЗмінна tokenЗапит Loginpost-response записує токен з відповідіпідстановка Bearer у заголовокpost-response перевіряє статус і тілоЗапит Get ordersЗмінна tokenЗапит LoginЗапит Get ordersЗмінна tokenЗапит Loginpost-response записує токен з відповідіпідстановка Bearer у заголовокpost-response перевіряє статус і тіло

    Тут є пастка зі скоупами, яка коштує новачкам години дебагу. Для запису стану ланцюжка потрібен персистентний скоупpm.collectionVariables.set або pm.environment.set: такі змінні переживають до наступного запиту в прогоні. А ось pm.variables.set створює локальну змінну: вона ніде не зберігається й при надсиланні запитів по одному — а саме так ланцюжок збирають і дебажать — зникає одразу після поточного запиту. У межах одного прогону раннера локальні змінні формально доживають до його кінця, але будувати на них стан ланцюжка не варто: поза раннером він розсипається. Симптом плутанини класичний: «я ж записав userId, чому в наступному запиті він порожній?» — бо записав не в той скоуп.

    Друга, глибша річ, яку варто розуміти: ланцюжок — це порядок-залежні запити. Запит Get orders не має сенсу, якщо перед ним не відпрацював Login, бо {{token}} ще не заповнений. Це зручно для наскрізного сценарію, але робить окремий запит невідтворюваним у відриві від ланцюжка й ускладнює ізоляцію. Та сама проблема порядку й спільного стану — наскрізна для автоматизації взагалі; докладніше про залежність тестів і підготовку стану — у главі «Тестові дані та стан в API-тестах».

    Collection Runner і data-driven

    Досі один запит перевіряв один випадок. Але техніки тест-дизайну — класи еквівалентності, граничні значення — вимагають прогнати той самий запит на багатьох входах: валідний email, порожній, без @, наддовгий, з unicode. Копіювати запит десять разів — марно. Правильний шлях — data-driven прогін: один запит, зовнішня таблиця входів, ітерація по рядках.

    Робить це Collection Runner — вбудований раннер, який жене колекцію (або папку) запитами по порядку. Йому можна згодувати файл даних (data file) у двох форматах:

    ФорматСтруктураКоли зручніший
    CSVПерший рядок — імена змінних, далі рядок = ітераціяТабличні набори, легко вести в таблиці
    JSONМасив обʼєктів, кожен обʼєкт = ітераціяВкладені дані, масиви, необхідні типи

    Runner робить одну ітерацію на рядок файлу. Значення з поточного рядка доступні або прямо в запиті через {{email}}, або в скрипті через pm.iterationData.get("email"). Ключовий прийом — покласти в таблицю не лише вхід, а й очікуваний результат, і звіряти з ним у пості:

    email,password,expectedStatus
    qa@example.com,Valid123!,200
    qa@example.com,wrong,401
    not-an-email,Valid123!,422
    ,Valid123!,422
    // post-response, той самий для всіх ітерацій
    pm.test(`Статус відповідає очікуваному для ${pm.iterationData.get("email")}`, () => {
        const expected = Number(pm.iterationData.get("expectedStatus"));
        pm.expect(pm.response.code).to.eql(expected);
    });

    Один запит, чотири рядки — чотири незалежні кейси в одному прогоні, і кожен підписаний своїм входом у звіті. Це прямий місток від таблиць тест-дизайну до виконання; як добирати самі рядки для API — у главі «Тест-дизайн для API: CRUD, параметри, межі».

    Одне застереження: ітерації йдуть послідовно й ділять персистентні змінні. Якщо в колекції одночасно живуть ланцюжок ({{token}} зі спільного скоупу) і data-driven таблиця, друга ітерація бачить те, що лишила перша. Іноді це те, що треба; частіше — джерело плутанини. Тримай стан ланцюжка й дані ітерації свідомо роздільно.

    Mock-сервери та examples

    Postman вміє не лише надсилати запити, а й відповідати на них. Основа тут — збережені приклади (examples), з якими ми познайомились у главі про основи: пара «запит + відповідь», прикріплена до запиту. Mock-сервер (mock server) — це хмарний ендпоінт, який Postman піднімає на основі цих прикладів: він приймає запит, підбирає відповідний приклад за методом і шляхом і віддає збережені статус, заголовки й тіло.

    Навіщо це QA й команді:

    • Фронтенд раніше за бекенд. Ендпоінта ще нема, а контракт узгоджений — мок дає фронтенду по чому працювати вже сьогодні.
    • Важковідтворювані відповіді. Змусити реальний сервіс віддати 500, специфічну помилку чи екзотичний крайовий формат буває складно; збережений приклад віддає його на замовлення.
    • Стабільні дані для тестів. Мок не залежить від стану БД і не «пливе» між прогонами.

    Головне обмеження чесно назвати одразу: мок не має логіки. Він віддає те, що ти в нього поклав, і не перевіряє тіло запиту, не рахує, не змінює стан. Тому мок годиться, щоб перевірити форму взаємодії й розблокувати роботу, але не поведінку. Це вбудований, найлегший рівень мокання; серйозніше мокання на рівні сервісів (WireMock, msw) і генерація мока зі специфікації (Prism з OpenAPI) — окрема велика тема глави «Мокання залежностей: стаби, mock-сервери, вебхуки», а специфікація як джерело такого мока — у главі «OpenAPI/Swagger: специфікація як джерело істини».

    Newman: запуск колекції в CI

    Collection Runner — це кнопка в застосунку, потрібна людина. Щоб та сама колекція крутилась автоматично — на кожен push, щоночі, у пайплайні, — існує Newman: офіційний CLI-раннер колекцій Postman, npm-пакет. Ідея проста: усе, що робить Runner в UI, Newman робить із командного рядка, а отже, його можна поставити в CI.

    Робочий цикл: експортуєш колекцію та середовище як JSON, комітиш їх у репозиторій — і запускаєш.

    # базовий прогін колекції з середовищем і файлом даних
    newman run collection.json -e staging.postman_environment.json -d data.csv

    Кілька прапорців, які варто знати:

    • -e — файл середовища (environment) з базовим URL і змінними стенда.
    • -d — файл даних для data-driven прогону (той самий CSV/JSON, що й у Runner).
    • -n — кількість ітерацій (скільки разів прогнати колекцію); працює і з файлом даних, і без нього.
    • -r — репортери: вбудовані cli, json, junit; популярний htmlextra ставиться окремим пакетом і дає читабельний HTML-звіт.

    Найважливіше для CI — код виходу (exit code). Якщо хоч один pm.test упав або запит не дійшов, Newman завершується з ненульовим кодом. Саме це перетворює колекцію на гейт: CI-крок падає, і пайплайн не пускає зміну далі. Без цього прогін нічого не «тримає» — тести крутяться, але нікого не зупиняють.

    0

    не 0

    collection.json + env.json + data.csv

    newman run

    exit code

    Крок зелений — пайплайн іде далі

    Крок червоний — гейт тримає

    0

    не 0

    collection.json + env.json + data.csv

    newman run

    exit code

    Крок зелений — пайплайн іде далі

    Крок червоний — гейт тримає

    Механіка самого пайплайна — де саме поставити цей крок, як передавати артефакти між стадіями, коли ганяти smoke, а коли повний набір — належить розділу про Git і CI/CD; тут важливо лише, що Newman дає рівно те, що CI вміє споживати: команду й код виходу. Секрети (токени в env.json) при цьому не комітять — їх передають через змінні CI або прапорець --env-var, бо закомічений токен чи токен у логах прогону — це витік.

    Межі Postman: коли починається код

    Postman масштабується далеко, але не нескінченно. Він найсильніший у дослідженні API, ручних перевірках, швидкій регресії без інфраструктури й у тому, щоб поділитися сценарієм з людиною, яка не пише код. Ознаки, що ти вперся в стелю:

    • Складна логіка й повторюване використання. Спільні хелпери, які треба тягати між колекціями, обростають копіпастом — пісочниця не дає нормального модуля.
    • Code review тестової логіки. Колекція — це один великий JSON; дифи в пул-реквесті нечитабельні, і рецензент фізично не бачить, що змінилось у тесті.
    • Типізація, IDE, дебаг. Немає автодоповнення по схемі відповіді, немає нормальних брейкпоінтів, помилку в скрипті ловиш лише на прогоні.
    • Інтеграція з рештою. Підготувати стан через БД, перевикористати ті самі клієнти, що й UI-тести, зібрати все в один звіт — у Postman це або незручно, або неможливо.

    Коли ці болі починають переважати зручність — сценарій переносять у код: APIRequestContext у Playwright, supertest, звичайний fetch/axios. Той самий ланцюжок «логін → токен → захищений запит» у коді виглядає так:

    // той самий ланцюжок у Playwright: логін → токен → захищений запит
    const login = await request.post('/auth/login', { data: { email, password } });
    const { token } = await login.json();
    const orders = await request.get('/orders', {
      headers: { Authorization: `Bearer ${token}` },
    });
    expect(orders.status()).toBe(200);

    Тут є типи, версіонування по рядках, спільні фікстури й повний дебаг — усе, чого пісочниці бракує. Це не «Postman гірший»: це різні інструменти під різні задачі, і зрілий кандидат називає межу, а не доводить, що «Postman вміє все». Як влаштовані API-клієнти в коді, структура й ланцюжки — у главі «API-автотести в коді: клієнти, структура, ланцюжки», а огляд усього інструментарію — у главі «API-тестування: обʼєкт перевірки, рівні й інструменти». На практиці команди часто тримають і те, й те: Postman для дослідження й ручних перевірок, код — для автоматизованого набору.

    Типові помилки

    • Виглядає як «змінна порожня в наступному запиті», а насправді не той скоуп. Значення записали через pm.variables.set — локальну змінну, яка при ручному надсиланні запитів не доживає до наступного. Для ланцюжка потрібен pm.environment.set або pm.collectionVariables.set.
    • Виглядає як «Newman зелений, отже, все добре», а насправді порожні перевірки. pm.test без жодного асерта завжди passed; прогін зелений, а нічого не перевіряється. Кожна перевірка має містити хоча б один pm.expect — і не лише на статус, а й на тіло (пастка «200 OK з помилкою в тілі» розібрана в главі «Перевірки відповіді»).
    • Виглядає як «CI не реагує на фейл», а насправді загублений exit code. Крок обгорнули в || true, або скрипт-обгортка губить код виходу Newman — пайплайн зелений попри червоні тести. Гейт тримає лише ненульовий код виходу.
    • Виглядає як «стабільний ланцюжок», а насправді порядок-залежність. Запусти запит із середини колекції окремо — падає, бо {{token}} ще не заповнений. Data-driven зі спільними змінними множить це: ітерації бачать стан одна одної.
    • Виглядає як «мок = бекенд», а насправді статичний приклад. Mock-сервер віддає збережену відповідь і не має логіки: не читає тіло запиту, не змінює стан. Ним перевіряють форму взаємодії, не поведінку.
    • Виглядає як «токен у Newman безпечний», а насправді витік. env.json з реальним токеном закомічено в git або токен світиться в логах прогону. Секрети — через CI-змінні чи --env-var, ніколи в репозиторій.

    Підсумок

    • Скрипти працюють у двох точках — pre-request готує запит, post-response перевіряє відповідь і витягує з неї дані — і виконуються в обмеженій пісочниці через обʼєкт pm, не в повному Node.
    • Ланцюжки будуються на персистентних змінних (environment/collection), а не на локальних pm.variables; їхня ціна — порядок-залежність запитів.
    • Collection Runner з файлом CSV/JSON дає data-driven прогін: один запит, багато рядків-кейсів, у кожному рядку — вхід і очікуваний результат.
    • Newman виносить колекцію в CI; гейт тримає саме ненульовий код виходу, а не сам факт прогону.
    • Postman масштабується до межі; складна логіка, code review й типізація — сигнал переносити сценарій у код.

    Що питають на співбесіді

    • «Як у Postman передати дані з однієї відповіді в наступний запит?» — очікують: у post-response витягти значення через pm.response.json(), записати в персистентну змінну (pm.collectionVariables.set/pm.environment.set), використати в наступному запиті через {{...}}. Плюс розуміння, чому саме персистентний, а не локальний скоуп.
    • «Чим відрізняються pre-request і post-response скрипти?» — перевіряють, чи розумієш ти момент виконання (до/після надсилання) і типове призначення кожного: підготовка запиту проти перевірки й екстракції.
    • «Що таке data-driven тестування в Postman і як його зробити?» — Collection Runner + файл даних CSV/JSON, одна ітерація на рядок, доступ через pm.iterationData; сильна відповідь звʼязує це з класами еквівалентності й межами.
    • «Що таке Newman і навіщо він?» — CLI-раннер колекцій для CI; ключове — код виходу як гейт і те, що колекція та середовище живуть у репозиторії як JSON.
    • «Коли Postman перестає вистачати?» — питання на зрілість: інтервʼюер хоче почути чесну межу (складна логіка, review, типізація, інтеграція → код), а не «Postman вміє все».

    Загальний фільтр тут — чи бачиш ти Postman як систему прогону (скрипти, скоупи, ітерації, гейт у CI), чи лише як «клікалку для запитів».

    Джерела