1. Smoke-набор: смысл и размер
Smoke-набор звучит как «что-то про пожарную безопасность», и это, честно говоря, очень точная метафора. Это не полноценное тестирование, не «покрыть всё», не «проверить каждый край». Smoke-набор — это быстрый ритуал: несколько запросов, которые отвечают на один вопрос: «всё ещё работает базовый сценарий, или уже горит и пахнет проводкой?».
Когда вы работаете с внешним API, особенно учебным курсом (где вы не контролируете провайдера), у вас постоянно есть риск, что что-то изменится: поле переименовали, путь стал другим, сервис начал отдавать другой статус, или просто временно «лежит». Если ваша коллекция — это 60 запросов, половина из которых «test2-final-final-really», то при проблеме вы будете заниматься не диагностикой, а археологией.
Именно поэтому smoke-набор должен быть коротким. Его ценность не в количестве сценариев, а в том, что вы можете запускать его часто и без внутреннего сопротивления. Если проверка занимает две минуты, вы делаете её перед тем, как что-то менять, после того как поменяли, и когда «что-то странное». Если проверка занимает полчаса, вы её… ну, вы знаете: «сделаю завтра» — это официальный статус для «никогда».
В контексте нашего проекта ReadLater Starter smoke-набор — это минимальная «карта входа» в контракт внешнего каталога. Он нужен, чтобы вы в любой момент могли подтвердить: поиск работает, детали по найденному идентификатору работают, и хотя бы парочка негативных границ ведёт себя предсказуемо. Это будет вашим якорем, когда вы перестанете смотреть на API через UI Postman и начнёте воспринимать его как договор, на который опирается код.
2. Нужные сценарии каталога для проекта
Если забыть про романтику и говорить практично, ReadLater Starter в клиентской фазе делает две вещи: ищет книгу и получает её детали. Всё остальное — «может быть когда-нибудь», а smoke-набор — это не место для «когда-нибудь». Это место для «обязательно».
Чтобы не расплываться, удобно заранее зафиксировать минимальную матрицу: какие запросы мы оставляем, какие переменные они используют, и какой результат мы считаем нормальным. Ниже — хороший «скелет» smoke-набора для нашего каталога в нейтральных терминах (без привязки к конкретному провайдеру).
| Сценарий | Назначение | Метод | URL-шаблон | Зависимости | Что считаем успехом |
|---|---|---|---|---|---|
| Search / happy-path | Найти хотя бы один результат | GET | {{baseUrl}}/search?q={{searchQuery}}&limit={{limit}} | baseUrl, searchQuery, limit | корректный статус, понятная структура, items не пустой |
| Details / happy-path | Получить детали по найденному id | GET | {{baseUrl}}/books/{{bookId}} | bookId берём из Search | корректный статус, объект с id, title (и др. полями) |
| Search / negative / empty-query | Понять границу «пустой ввод» | GET | {{baseUrl}}/search?q= | baseUrl | предсказуемый ответ: либо ошибка, либо пустые результаты |
| Details / negative / missing-id | Понять границу «ресурс не найден» | GET | {{baseUrl}}/books/{{missingBookId}} | baseUrl, missingBookId | предсказуемый not-found (часто 404) либо стабильная error-форма |
Обратите внимание на одну тонкую штуку: smoke-набор в нашем случае проверяет не «правильность данных» (что книга действительно “Clean Code”), а «живость контракта». Мы не экзаменуем каталог, мы проверяем, что мы сами не потеряли понимание его формы. Это важная настройка мышления: в API-контракте главный герой — структура и семантика ответа, а не конкретные значения конкретной записи.
3. Оформление коллекции: структура и порядок
Коллекция в Postman — это не просто папка «где лежат запросы». В нормальной жизни это часть документации, часть воспроизводимости и часть вашего будущего спокойствия. Поэтому мы сейчас делаем маленькую, но важную вещь: превращаем «набор запросов» в «читаемую историю».
Самый простой и рабочий формат — сделать внутри коллекции одну папку Catalog (smoke) и внутри разложить запросы так, чтобы глазами было видно сценарий и порядок. Порядок важен не «потому что красиво», а потому что у нас есть chained flow: Details зависит от Search, который сохраняет bookId. Если вы случайно отправите Details первым — вы получите ошибку, которая не говорит о проблеме каталога, она говорит о проблеме вашей дисциплины.
Вот пример структуры, которая хорошо читается и почти не требует объяснений:
ReadLater / External Catalog
└─ Catalog (smoke)
├─ 01 - Search - happy-path
├─ 02 - Details - happy-path (uses {{bookId}})
├─ 03 - Search - negative - empty query
└─ 04 - Details - negative - missing id (uses {{missingBookId}})
Да, префиксы 01, 02 — это немного «олдскульно». Зато это работает даже тогда, когда Postman решит отсортировать запросы по алфавиту, а вы спустя неделю откроете коллекцию «после работы, без кофе, но с проблемой». Это тот случай, когда маленькая формальность экономит много нервов.
Ещё один практичный штрих — подписи. В Description каждого запроса (в Postman есть поле описания у запроса) стоит написать одну-две фразы: что проверяем и что ожидаем увидеть. Не роман, а «шпаргалка для будущего себя». Будущий вы вам за это скажет спасибо. Иногда — даже вслух.
4. Переменные окружения и переносимость
Smoke-набору не нужен полный зоопарк переменных. Ему нужен один конкретный инвентарь, чтобы happy-path и negative-path запускались одинаково предсказуемо: baseUrl, searchQuery, limit, bookId, missingBookId.
Вот как может выглядеть окружение:
{
"baseUrl": "https://catalog.example.com",
"searchQuery": "clean code",
"limit": "3",
"bookId": "",
"missingBookId": "DOES_NOT_EXIST"
}
bookId — рабочая переменная happy-path цепочки: её заполняет поиск, а детали подхватывают. missingBookId задаётся руками один раз и нужен только для стабильного not-found сценария. Правило про baseUrl остаётся тем же: без завершающего /, чтобы шаблоны URL не ломались на склейке.
Если появится второй стенд, делайте второе окружение с теми же именами переменных, а не новый словарь. Тогда smoke-набор переключается одним кликом, а не через ручную замену полколлекции.
5. Happy-path: Search → bookId → Details
Самый «живой» кусок smoke-набора — связанный happy-path. Но здесь нам не нужен ещё один полный разбор Tests-скрипта. Для финальной сборки важно одно: 01 - Search - happy-path должен оставлять после себя свежий bookId, а при пустом результате не должен тихо тянуть значение из прошлого запуска. Тогда 02 - Details - happy-path действительно проверяет второй шаг контракта, а не случайный старый ID.
URL второго шага остаётся простым:
{{baseUrl}}/books/{{bookId}}
Если Details внезапно перестал работать, первым делом смотрим не только на сам endpoint, но и на то, что именно сохранил Search. В chained-smoke это такая же часть диагностики, как status code.
flowchart LR A["01 Search (happy)"] -->|Tests: set bookId| B[(Environment)] B --> C["02 Details (happy)"]
6. Negative-path: два самых полезных запроса
Negative-path в smoke-наборе нужен не чтобы «помучить API». Он нужен, чтобы увидеть границы контракта. У внешнего каталога почти всегда есть два типа негативных ситуаций, которые полезно понимать: плохой/пустой ввод в поиске и запрос детали по несуществующему ресурсу.
В запросе 03 - Search - negative - empty query мы намеренно отправляем пустой запрос. Важно: это не «сломать интернет», это проверить поведение контракта. Иногда сервис вернёт 400 Bad Request, иногда — 200 OK с пустым списком, иногда — «всё подряд». Любой из этих вариантов может быть допустимым, если он стабилен, а вы его зафиксировали.
{{baseUrl}}/search?q=
В запросе 04 - Details - negative - missing id лучше использовать не строку, вбитую руками, а переменную missingBookId. Тогда not-found сценарий остаётся таким же воспроизводимым, как happy-path: смысл живёт в имени переменной, а не в случайном тексте URL.
{{baseUrl}}/books/{{missingBookId}}
Почему эти два запроса так полезны? Потому что они помогают отличать «контракт так устроен» от «сервис сломался». Если вы вдруг получаете странный ответ на happy-path, вы можете быстро посмотреть negative-path. Если negative-path тоже стал странным (например, вместо предсказуемого 404 вы получаете 500), скорее всего проблема на стороне провайдера или сеть. Если negative-path стабилен, а happy-path нет — вы, возможно, поменяли searchQuery, limit или структуру цепочки.
Здесь важен психологический момент: smoke-набор не обязан «падать красиво». Он обязан быть понятным. Два негативных запроса — это как две контрольные точки на карте: «вот здесь должен быть обрыв», «вот здесь должна быть пустыня». Если внезапно на месте пустыни океан, возможно, вы заблудились.
7. Sample JSON и фиксация контракта
Smoke-набор не придумывает отдельное хранилище для sample JSON. Он опирается на те же файлы в src/main/resources/mock/catalog/: search-success.json, search-empty.json, details-success.json и, если провайдер действительно отдаёт JSON body на not-found, details-not-found.json.
Если на {{missingBookId}} приходит только статус и пустое тело, не выдумывайте отдельный JSON ради симметрии. Достаточно короткой пометки рядом с файлами, что этот сценарий фиксируется статусом и отсутствием body.
У каждого smoke-запроса полезно оставить короткую связь с соответствующим sample в Description. Example в Postman можно сохранить как визуальную подсказку, но основной опорой для mock-режима остаются файлы в репозитории.
8. Ритуал запуска smoke-набора
Smoke-набор хорош только тогда, когда он используется. Поэтому у него должен быть «ритуал запуска» — простой и одинаковый каждый раз. Без мистики, без героизма, без «сейчас три часа настрою, чтобы один раз проверить».
Практический сценарий выглядит так. Вы выбираете активное окружение (проверяете это глазами, потому что Postman любит тихо оставаться в прошлом окружении). Потом открываете 01 - Search - happy-path, нажимаете Send, смотрите статус и форму ответа, и убеждаетесь, что bookId сохранился (это можно увидеть в Environment, где переменные, или в Postman Console, если вы сделали console.log). Затем открываете 02 - Details - happy-path, нажимаете Send и проверяете, что это действительно карточка по тому id, который вы только что нашли.
После этого вы делаете два негативных запроса. Не для того чтобы «просто было», а чтобы закрепить границы: пустой поисковый запрос и несуществующий идентификатор. И всё. На этом smoke-набор закончен. Он не должен утомлять. Он должен дать вам уверенность: «контракт жив и понятен».
Если вы обнаружили, что что-то поменялось, вы не пытаетесь «починить всё». Вы фиксируете наблюдение: какой статус теперь приходит, какая форма body, какие поля исчезли/появились. И обновляете sample JSON, если изменение похоже на реальное изменение контракта, а не на случайный сбой. Это и есть дисциплина контракта: не паниковать, а аккуратно записывать, что мир сделал сегодня.
9. Типичные ошибки smoke-набора Postman
Ошибка №1: превращать smoke-набор в музей всех экспериментов.
Очень легко оставить в папке “smoke” всё подряд: «проверка заголовков», «попытка с другим параметром», «а вот тут я смотрел, что будет если…». Через неделю это перестаёт быть smoke-набором и становится свалкой. Держите в “smoke” только те запросы, которые вы готовы запускать регулярно, быстро и без внутреннего сопротивления.
Ошибка №2: зависимый запрос стоит раньше источника данных.
Если Details лежит выше Search, вы почти гарантированно однажды нажмёте не туда, получите ошибку и потратите время на выяснение «что сломалось». Это не баг API, это баг порядка. Нумерация 01/02 или хотя бы явный порядок в названии — простой способ сделать коллекцию дружелюбной к человеку, который устал.
Ошибка №3: baseUrl живёт в разных форматах, и запросы ломаются на “склейке”.
Когда в одном окружении baseUrl заканчивается на /, а в другом — нет, вы ловите то двойной слеш, то “слипшиеся” части URL. Это очень раздражающий класс ошибок, потому что он выглядит как «API упало», хотя на самом деле упала ваша строка. Один раз договоритесь: baseUrl без завершающего слеша — и дальше не спорьте с собой.
Ошибка №4: chained request неустойчив к пустому результату поиска.
Если в Tests вы сразу пишете body.items[0].id, то при пустом списке вы получите ошибку скрипта, а не честное поведение. Smoke-набор тем и хорош, что он должен быть спокойным и предсказуемым: нет items — значит, не сохраняем bookId и не притворяемся, что мир обязан дать нам результат.
Ошибка №5: negative-path выбран случайно и иногда становится happy-path.
Запрос books/BK-999 кажется «точно не существует», пока однажды не оказывается, что существует. И вы внезапно «проверяете not found», а получаете нормальный объект — и начинаете подозревать, что каталог сломан. Для negative-path выбирайте заведомо искусственные значения вроде DOES_NOT_EXIST, чтобы сценарий не зависел от случайной реальности данных.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ