QA Automation на Java: от основ до CI

Курс поможет начать автоматизацию тестирования на Java: от базовых принципов и написания автотестов до работы с API, UI и интеграции в CI/CD. Подойдет тем, кто знаком с основами программирования и хочет применить Java в QA Automation.

1. Роль QA Automation и база тестирования

Роль QA Automation и база тестирования

Что делает QA и зачем нужна автоматизация

QA (Quality Assurance) — это системная работа над тем, чтобы продукт был качественным: процессы, требования, проверки, обратная связь. Тестирование — часть QA, сфокусированная на выявлении несоответствий между ожидаемым и фактическим поведением.

QA Automation решает задачу: быстро и воспроизводимо проверять продукт с минимальными ручными усилиями, особенно там, где проверки повторяются или критичны к регрессии.

Важно: автоматизация не заменяет ручное тестирование полностью. Она лучше всего работает для стабильных, повторяемых сценариев и проверок, где ценны скорость и регулярность.

Чем автотесты отличаются от ручных

Ручное тестирование:

  • сильное в исследовательских проверках (поиск неожиданных дефектов);
  • гибкое при частых изменениях интерфейса/логики;
  • дешевле “на старте”.
  • Автотесты:

  • дают быстрый регресс (часто — на каждый коммит/сборку);
  • фиксируют ожидаемое поведение как “исполняемую спецификацию”;
  • требуют затрат на разработку, поддержку и инфраструктуру.
  • Ключевой критерий: автотесты окупаются, когда экономят время команды чаще, чем требуют времени на поддержку.

    Что стоит (и не стоит) автоматизировать

    Автоматизировать стоит, когда сценарий:

  • повторяется (регресс, смоук, sanity);
  • критичен для бизнеса (оплата, логин, оформление заказа);
  • проверяет много комбинаций данных;
  • сложно проверять вручную или высок риск человеческой ошибки.
  • Не стоит автоматизировать, когда:

  • функциональность нестабильна и постоянно меняется;
  • проверка субъективна (визуальная эстетика, удобство);
  • стоимость поддержки выше выгоды (слишком “хрупкий” UI при частых правках).
  • Базовые уровни тестирования и “пирамида”

    Одна из практичных моделей — распределять проверки по уровням: чем ниже уровень, тем тесты быстрее и дешевле в поддержке.

  • Модульные (unit): проверяют небольшие части логики изолированно. Быстрые, стабильные.
  • Интеграционные: проверяют взаимодействие компонентов (например, сервис + база данных, сервис + внешний API).
  • UI/E2E: проверяют пользовательские сценарии “сквозняком” через интерфейс. Дороже, медленнее, но полезны для критичных потоков.
  • Для QA Automation важно понимать: UI-тесты не должны быть единственной опорой. Если ими покрыть всё, регресс станет медленным и хрупким.

    Типы тестирования, которые часто автоматизируют

  • Регрессионное: проверка, что старое не сломалось после изменений.
  • Smoke: минимальный набор проверок “система жива”.
  • API-тестирование: проверка контрактов и бизнес-правил на уровне сервисов.
  • UI-тестирование: проверка ключевых пользовательских сценариев.
  • Нефункциональные проверки: частично автоматизируются (например, базовые проверки производительности или стабильности), но требуют отдельного подхода.
  • База тест-дизайна: как выбирать проверки

    Автотест — это не “запись кликов”, а осмысленная проверка. Основные идеи тест-дизайна помогают выбрать минимальный набор сценариев с максимальной пользой.

    1) Классы эквивалентности

    Если система одинаково обрабатывает целые группы входных данных, достаточно проверить по одному представителю из группы.

    Пример: поле “Возраст” принимает 1–120.

  • валидный класс: 1–120
  • невалидные классы: меньше 1, больше 120, не число
  • 2) Граничные значения

    Ошибки часто прячутся на границах диапазонов.

    Пример для 1–120: проверяем 1, 120 и рядом с ними (0, 121).

    3) Таблицы решений (комбинации условий)

    Когда результат зависит от набора условий, полезно явно перечислить комбинации.

    Пример (упрощённо): скидка зависит от “клиент VIP” и “сумма > 10 000”. Не нужно тестировать случайные комбинации — лучше покрыть все логически значимые.

    Что такое “хороший” автотест

    Хороший автотест обычно:

  • детерминированный: даёт одинаковый результат при одинаковых условиях;
  • быстрый: не делает лишнего (особенно на UI);
  • понятный: по имени и шагам ясно, что проверяется;
  • изолированный: не зависит от результатов других тестов;
  • поддерживаемый: изменения в продукте требуют минимальных правок в тестах.
  • Полезная практика: в одном тесте проверять одну основную причину падения. Тогда по отчёту легче понять, что сломалось.

    Минимальная тестовая документация, которая помогает автоматизации

    Чтобы автотесты были осмысленными, важно иметь хотя бы:

  • требования/ожидания: что считается правильным;
  • критичные пользовательские потоки: что нельзя сломать;
  • тестовые данные: какие данные нужны и как их готовить;
  • критерии готовности: когда проверка считается пройденной.
  • Даже если нет формальных спецификаций, ожидания можно фиксировать как короткие сценарии в формате “Дано–Когда–Тогда” (без привязки к инструментам).

    ---

    Задания для закрепления

    1) Объясните разницу между QA и тестированием в одном-двух предложениях.

    2) Для сценария “Авторизация пользователя” предложите:

  • один пример smoke-проверки;
  • один пример регрессионной проверки.
  • 3) Поле “Сумма перевода” принимает значения от 10 до 50 000. Подберите 6 значений для проверки граничных условий и объясните выбор.

    4) Назовите по 2 причины, почему UI/E2E-тесты обычно дороже в поддержке, чем API-тесты.

    5) Опишите один сценарий, который не стоит автоматизировать на раннем этапе, и почему.

    6) Есть правило: “тест должен падать по одной основной причине”. Приведите пример, как нарушение этого правила усложняет диагностику.

    <details> <summary> Ответы </summary>

    1) QA — это обеспечение качества через процессы и практики в целом; тестирование — часть QA, направленная на проверку продукта и поиск несоответствий.

    2) Пример smoke для авторизации: пользователь с валидными учётными данными может войти и увидеть главную страницу/профиль.

    Пример регресса: после изменений в системе паролей пользователь по-прежнему может войти, а неверный пароль по-прежнему даёт корректную ошибку и не пускает.

    3) Диапазон 10–50 000. Границы и рядом:

  • 9 (ниже минимума)
  • 10 (минимум)
  • 11 (рядом с минимумом)
  • 49 999 (рядом с максимумом)
  • 50 000 (максимум)
  • 50 001 (выше максимума)
  • 4) Причины дороговизны UI/E2E:

  • они зависят от верстки, локаторов, времени загрузки и “хрупких” элементов интерфейса;
  • они медленнее (браузер/рендеринг), чаще страдают от нестабильности окружения и требуют больше инфраструктуры.
  • Причины, почему API дешевле:

  • интерфейс API обычно стабильнее, тесты быстрее и менее зависимы от визуальных изменений.
  • 5) Не стоит автоматизировать часто меняющийся прототип интерфейса (например, экран, который каждый день переделывают). Поддержка тестов будет съедать больше времени, чем приносить пользы.

    6) Пример нарушения: тест “оформление заказа” в одном сценарии проверяет логин, поиск товара, добавление в корзину, оплату и письмо на почту. Если он упал, непонятно, что именно сломалось (логин? корзина? платёж? нотификации?). Разделение на более точечные проверки ускоряет диагностику.

    </details>

    2. Java для автотестов: синтаксис и ООП минимум

    Java для автотестов: синтаксис и ООП минимум

    Автотесты на Java — это обычный Java‑код: классы, методы, объекты, коллекции, исключения. Ниже — минимальный набор языка, без которого сложно писать поддерживаемые UI/API тесты и тестовую инфраструктуру.

    1) Базовый синтаксис, который встречается в тестах каждый день

    Переменные и типы

    Java — статически типизированный язык: тип переменной известен компилятору.

    Практика для автотестов:

  • String — локаторы, урлы, сообщения ошибок.
  • int/long — статусы, ID.
  • boolean — флаги условий.
  • Списки/карты — наборы данных, параметры запросов.
  • Условия и циклы

    В тестах условия полезны для ветвлений в хелперах, а циклы — для параметризации данных.

    Методы

    Метод — основной способ прятать детали (например, шаги авторизации) и переиспользовать код.

    Ключевые части:

  • public — модификатор доступа.
  • String — возвращаемый тип.
  • buildAuthHeader — имя.
  • (String token) — параметры.
  • Перегрузка (overload)

    Один метод с разными параметрами — удобно для “короткой” и “полной” версии шага.

    2) Классы и объекты: база для тестовой архитектуры

    Класс, поля, конструктор

    Класс описывает “что такое объект”, объект — конкретный экземпляр.

    Почему это важно в тестах:

  • private скрывает детали (инкапсуляция).
  • final помогает делать тестовые данные неизменяемыми → меньше случайных побочных эффектов.
  • геттеры дают контролируемый доступ.
  • static vs экземпляр

  • Экземплярные методы работают с состоянием объекта.
  • static — принадлежит классу и не требует создания объекта.
  • Практика: static final часто используют для констант (таймауты, базовые пути). Но избегайте “глобального состояния” (например, static драйвер браузера без контроля жизненного цикла).

    3) ООП минимум: 4 идеи, которые реально применяются

    Инкапсуляция

    Скрываем реализацию за публичным API класса.

    Пример: в UI тестах Page Object прячет локаторы и действия внутри класса страницы, наружу отдаёт понятные методы.

    Наследование

    Используйте осторожно. Иногда удобно иметь базовый класс для общих вещей (логирование, общие проверки), но чрезмерная иерархия делает код хрупким.

    Полиморфизм

    Код работает с “общим типом”, а конкретная реализация подменяется.

    Практика: интерфейсы помогают делать тесты гибкими (например, в локальном окружении — один провайдер, в CI — другой).

    Абстракция

    Выделяем главное, прячем детали.

    В автотестах хорошая абстракция: “клиент API”, “страница логина”, “генератор тестовых данных”, а не “набор случайных утилит”.

    4) Коллекции и дженерики (Generics)

    Чаще всего используются:

  • List<T> — упорядоченный список.
  • Set<T> — уникальные значения.
  • Map<K, V> — ключ‑значение (например, заголовки запроса).
  • Дженерики (<T>) делают коллекции типобезопасными:

    Это важно: компилятор не даст случайно положить в headers число вместо строки.

    5) Исключения: как “правильно падать” в автотестах

    В тестовой автоматизации исключение — это нормальный способ остановить выполнение при критичной проблеме.

  • RuntimeException (непроверяемые) часто удобны для утилит.
  • Проверяемые (Exception) требуют throws или try/catch.
  • Практика: бросайте исключения с понятным сообщением — это ускоряет диагностику падений в CI.

    6) Равенство объектов: == vs equals()

  • == сравнивает ссылки (это один и тот же объект в памяти?).
  • equals() сравнивает “смысл” (зависит от реализации класса).
  • В тестах почти всегда для строк и доменных объектов нужен equals().

    Если вы создаёте свои классы данных (например, User), продумайте, нужно ли переопределять equals()/hashCode() — иначе сравнение объектов будет по ссылке.

    ---

    Задания для закрепления

    1) Объясните разницу между == и equals() на примере String.

    2) Напишите сигнатуру метода buildUrl, который принимает baseUrl и path и возвращает String.

    3) Почему поля тестовых данных часто делают private final?

    4) Выберите подходящую коллекцию: List, Set или Map.

  • список ролей пользователя без требования уникальности
  • набор уникальных тегов теста
  • заголовки HTTP запроса
  • 5) Что такое интерфейс и зачем он может понадобиться в тестах?

    6) В каком случае логичнее бросить IllegalArgumentException?

    <details> <summary> Ответы </summary>

    1) == для String сравнит, один и тот же ли это объект (ссылка). equals() сравнит содержимое строки. В тестах обычно нужно сравнение содержимого, значит equals().

    2) Например: public String buildUrl(String baseUrl, String path).

    3) private — инкапсуляция (нельзя менять напрямую снаружи). final — объект становится неизменяемым после конструктора, меньше риска случайно “испортить” тестовые данные в процессе выполнения.

    4)

  • роли без уникальности: List
  • уникальные теги: Set
  • заголовки запроса: Map<String, String>
  • 5) Интерфейс задаёт контракт (какие методы должны быть), но не привязывает код к конкретной реализации. В тестах это помогает подменять реализации (реальный провайдер токена vs заглушка), упрощает конфигурацию и поддержку.

    6) Когда аргумент метода некорректен с точки зрения ожиданий метода: null там, где нельзя, пустая строка, отрицательный таймаут и т.п.

    </details>

    3. Инструменты проекта: Maven/Gradle, Git, JUnit 5

    Инструменты проекта: Maven/Gradle, Git, JUnit 5

    Инструменты ниже — базовый «скелет» любого Java‑проекта автотестов: сборка (Maven/Gradle), контроль версий (Git) и тестовый фреймворк (JUnit 5). Синтаксис Java и общие принципы хорошего автотеста уже разбирались ранее — здесь фокус на том, как правильно организовать проект и запускать тесты.

    1) Maven и Gradle: зачем они в автотестах

    Сборщик решает три практичные задачи:

  • Скачивает зависимости (JUnit, RestAssured, Selenium и т.д.).
  • Компилирует код и запускает тесты одной командой.
  • Даёт единый формат проекта, понятный локально и в CI.
  • Структура проекта (общая логика)

    Чаще всего используется стандартная раскладка:

    Maven: ключевые понятия

  • pom.xml — описание проекта.
  • Dependencies — библиотеки, которые подтягиваются из репозитория.
  • Scope test — зависимость нужна только для тестов.
  • mvn test — типовая команда запуска.
  • Минимальный пример зависимостей для JUnit 5:

    Практика: если тесты «не видятся», часто причина в том, что JUnit 5 подключён, но плагин запуска тестов (Surefire) слишком старый.

    Gradle: ключевые понятия

  • build.gradle (Groovy) или build.gradle.kts (Kotlin) — описание проекта.
  • testImplementation — зависимости для тестов.
  • Важно включить платформу JUnit 5: useJUnitPlatform().
  • Минимальный пример:

    Запуск:

    Что выбрать: Maven или Gradle

    Оба подходят. Выбирайте то, что принято в команде. Если вы стартуете с нуля и важна «предсказуемость из коробки» — Maven часто проще. Если хочется более гибкой сборки и быстрого инкрементального выполнения — Gradle удобен.

    2) Git: базовые практики для автотестов

    Git нужен, чтобы:

  • Хранить историю изменений тестов и инфраструктуры.
  • Делать review (видно, что поменялось и зачем).
  • Параллельно развивать разные ветки (фичи/фиксы) без конфликтов в одной «копии».
  • Минимальный набор команд

    .gitignore для Java‑проектов

    Смысл — не коммитить то, что генерируется автоматически:

  • директории сборки (target/, build/),
  • IDE‑файлы,
  • локальные временные файлы.
  • Пример (идея, без привязки к конкретной IDE):

    Практика веток и коммитов

  • Коммит = логически цельное изменение (например, «добавил smoke‑набор авторизации»).
  • Сообщение коммита отвечает на вопрос «что сделано», а не «что нажал».
  • Ветки называйте так, чтобы по имени было понятно назначение: feature/..., fix/..., chore/....
  • 3) JUnit 5: база запуска и организации тестов

    JUnit 5 (JUnit Jupiter) — тестовый фреймворк, который:

  • обнаруживает тестовые методы,
  • запускает их,
  • предоставляет assertions,
  • управляет жизненным циклом.
  • Как JUnit понимает, что это тест

    Основные аннотации:

  • @Test — тестовый метод.
  • @BeforeEach / @AfterEach — подготовка и очистка перед/после каждого теста.
  • @BeforeAll / @AfterAll — один раз на класс (часто для поднятия тяжёлых ресурсов).
  • Важно: избегайте «общего состояния» между тестами. Если общий ресурс нужен (например, клиент API), делайте его безопасным и контролируйте жизненный цикл.

    Assertions: как правильно проверять

    Частые проверки:

  • assertEquals(expected, actual) — сравнение значений.
  • assertTrue(condition) / assertFalse(condition) — булевы проверки.
  • assertAll(...) — несколько проверок в рамках одного теста (полезно, когда нужно увидеть все расхождения разом).
  • assertThrows(...) — проверка ожидаемого исключения.
  • Практика: сообщение к ассёрту (или осмысленное имя теста) экономит время при падении в CI.

    Параметризованные тесты (когда нужно много данных)

    Вместо копипаста похожих тестов используйте параметризацию:

  • @ParameterizedTest
  • источники данных: @ValueSource, @CsvSource, @MethodSource
  • Это особенно полезно для проверок валидаций, наборов ролей, разных статусов API.

    Теги и выборочный запуск

  • @Tag("smoke"), @Tag("regression") помогают разделять наборы.
  • В CI часто запускают быстрый набор (smoke) чаще, а полный регресс — реже.
  • 4) Как это склеивается в реальный рабочий процесс

    Типовой цикл:

  • Создали/обновили тесты.
  • Запустили локально через Maven/Gradle.
  • Закоммитили изменения в Git в отдельной ветке.
  • После review и мержа тесты могут выполняться автоматически в CI.
  • ---

    Задания для закрепления

    1) Объясните, чем dependency со scope/test отличается от обычной зависимости.

    2) У вас не запускаются тесты JUnit 5 в Maven. Назовите две вероятные причины на уровне конфигурации проекта.

    3) Напишите минимальный набор файлов/папок, которые вы бы добавили в .gitignore для Java‑проекта автотестов.

    4) Когда разумно использовать @BeforeAll, а когда лучше @BeforeEach? Приведите по одному примеру.

    5) Для чего нужны @Tag и как вы бы применили их к наборам smoke/regression?

    6) У вас 10 однотипных тестов на валидацию поля. Какой механизм JUnit 5 уменьшит дублирование?

    <details> <summary> Ответы </summary>

    1) test означает, что библиотека нужна только на этапе тестов: она не должна попадать в runtime/production‑артефакты. Обычная зависимость доступна и в основном коде, и при запуске приложения.

    2) Две частые причины:

  • Подключили JUnit 5 зависимость, но используете старый плагин запуска тестов (например, Surefire), который не поддерживает JUnit 5.
  • Неверно подключили Gradle/Maven конфигурацию запуска (в Gradle забыли useJUnitPlatform(), в Maven неправильно настроили плагины/версии).
  • 3) Минимум:

  • Папки результатов сборки: target/, build/.
  • Логи: *.log.
  • Локальные секреты/переменные окружения: .env.
  • Служебные файлы IDE (по используемой IDE).
  • 4) @BeforeAll — когда ресурс дорогой и общий на класс (например, поднять тестовый контейнер БД один раз). @BeforeEach — когда нужна чистая подготовка перед каждым тестом (например, создать тестового пользователя или очистить состояние страницы/куки).

    5) @Tag позволяет маркировать тесты и запускать выборочно. Пример: критичные сценарии пометить @Tag("smoke") и запускать чаще, а полный набор — @Tag("regression") и запускать по расписанию/на релиз.

    6) Параметризованные тесты: @ParameterizedTest вместе с @ValueSource, @CsvSource или @MethodSource.

    </details>

    4. UI-автоматизация: Selenium WebDriver и Page Object

    UI-автоматизация: Selenium WebDriver и Page Object

    UI‑автотесты проверяют продукт «глазами пользователя» через браузер. Они полезны для критичных пользовательских потоков, но дороже и “хрупче”, чем API‑тесты, поэтому особенно важны правильные ожидания (waits) и архитектура (Page Object).

    1) Selenium WebDriver: что это и как он работает

    Selenium WebDriver — библиотека, которая управляет браузером через драйвер (ChromeDriver, GeckoDriver и т.д.). В тесте вы:

  • Поднимаете WebDriver (экземпляр браузера)
  • Открываете страницу
  • Находите элементы (локаторы)
  • Действуете (клик, ввод, выбор)
  • Проверяете результат (assert)
  • Минимальный «скелет» (без деталей сборки и JUnit — это было раньше):

    quit() закрывает браузер и освобождает ресурсы — это важно для стабильности локально и в CI.

    2) Локаторы: как «находить» элементы

    Локатор — способ однозначно указать элемент на странице.

    Частые варианты:

  • By.id("...") — обычно самый стабильный
  • By.cssSelector("...") — гибкий, удобен для классов/атрибутов
  • By.xpath("...") — мощный, но легко сделать хрупким
  • Практичные правила:

  • Предпочитайте стабильные атрибуты, а не верстку.
  • Если есть возможность — договоритесь с командой о data-testid/data-qa.
  • Избегайте локаторов, завязанных на текст, если текст локализуется/меняется.
  • Пример с data-testid:

    3) Ожидания (Waits): ключ к борьбе с флаками

    Большинство нестабильности UI‑тестов — из-за асинхронности: страница грузится, элементы появляются позже, запросы выполняются в фоне.

    Почему Thread.sleep() — плохая идея

    Thread.sleep(2000):

  • делает тесты медленнее
  • не гарантирует, что нужное состояние наступит/artificial
  • маскирует реальные проблемы синхронизации
  • Явные ожидания (Explicit Wait)

    Используйте WebDriverWait + условия (ExpectedConditions):

    Типовые условия:

  • visibilityOfElementLocated — элемент видим
  • elementToBeClickable — можно кликнуть
  • invisibilityOfElementLocated — исчез лоадер
  • urlContains / titleContains — навигация завершена
  • Практика: ждите состояние, а не “время”.

    4) Page Object: как сделать UI‑тесты поддерживаемыми

    Page Object — паттерн, где каждая страница/компонент UI описана отдельным классом:

  • локаторы и действия спрятаны внутри
  • тест читает сценарий на уровне “пользовательских шагов”
  • при изменении верстки правите один класс, а не десятки тестов
  • Базовая структура

    Пример Page Object для логина

    Ключевые идеи:

  • Page Object возвращает следующую страницу после действия (например, loginAsDashboardPage).
  • Внутри страницы — ожидания и локаторы, тест их не повторяет.
  • Методы называются “смыслово”: loginAs, open, getErrorText, а не clickButton1.
  • Что НЕ стоит класть в Page Object

  • Assertions уровня теста (например, “пользователь должен увидеть…”): лучше вынести в тест, чтобы падения были понятнее.
  • Сложную бизнес-логику подготовки данных: для этого обычно делают отдельные хелперы/клиенты.
  • 5) Типовые проблемы UI‑тестов и практики

  • StaleElementReferenceException: элемент перерендерился.
  • - Решение: не хранить WebElement надолго, заново искать по локатору; ждать нужного состояния.
  • Клик не проходит (оверлей/анимация).
  • - Решение: ждать elementToBeClickable, ждать исчезновения лоадера.
  • Тесты зависят друг от друга.
  • - Решение: каждый тест сам готовит предпосылки (пользователь, состояние), не полагается на предыдущий.

    ---

    Задания для закрепления

    1) Выберите лучший локатор из трёх вариантов и объясните почему:

  • By.xpath("//div[3]/button")
  • By.cssSelector(".btn.btn-primary")
  • By.cssSelector("[data-testid='checkout-submit']")
  • 2) Почему Thread.sleep() часто ухудшает стабильность и скорость UI‑тестов? Назовите 2 причины.

    3) Разбейте на Page Objects сценарий: «Открыть страницу логина → залогиниться → убедиться, что имя пользователя отображается в шапке». Какие классы и какие методы вы бы сделали?

    4) В тесте часто ловите StaleElementReferenceException после клика “Сохранить”. Назовите 2 практики, которые помогут.

    5) Где лучше хранить ожидания: в тесте или внутри Page Object? Коротко аргументируйте.

    <details> <summary> Ответы </summary>

    1) Лучший вариант — By.cssSelector("[data-testid='checkout-submit']"), потому что он обычно стабилен при изменениях верстки и стилей. XPath по позиции (div[3]) хрупкий, классы .btn.btn-primary часто переиспользуются и меняются при редизайне.

    2) Две причины:

  • sleep ждёт фиксированное время, даже если страница уже готова → тесты медленнее.
  • если страница не успела прогрузиться за это время, тест всё равно продолжит → флаки и случайные падения.
  • 3) Например:

  • LoginPage: open(baseUrl), loginAs(user, pass).
  • DashboardPage (или HeaderComponent): getDisplayedUserName() или header().getUserName().
  • Тест: создаёт LoginPage, логинится, получает DashboardPage, проверяет имя в шапке.

    4) Две практики:

  • Не хранить старый WebElement после действий, которые перерисовывают страницу; заново искать элемент по By.
  • Добавить явное ожидание состояния после клика (например, исчезновение лоадера/появление тоста/доступность кнопки) перед следующими шагами.
  • 5) Чаще лучше хранить ожидания внутри Page Object, потому что синхронизация — часть взаимодействия со страницей. Тогда тест остаётся «сценарием», а детали загрузки/рендера инкапсулированы. При этом редкие, специфичные ожидания уровня бизнес-проверки можно оставить в тесте.

    </details>

    5. API-тесты: REST Assured, JSON и проверки контрактов

    API-тесты: REST Assured, JSON и проверки контрактов

    API‑тесты проверяют сервис «без браузера»: запрос → ответ. По сравнению с UI они обычно быстрее и стабильнее (про уровни тестирования см. статью про пирамиду). На практике API‑автотесты часто становятся основой регресса: они хорошо ловят поломки контрактов и бизнес‑правил на уровне сервиса.

    1) Что именно проверяет API‑тест

    Минимальный полезный набор проверок (контракт + смысл):

  • HTTP‑статус (например, 200, 201, 400, 401).
  • Заголовки: Content-Type, иногда Cache-Control, Location, ETag.
  • Тело ответа: обязательные поля, типы данных, значения, сообщения ошибок.
  • Правила: например, «после создания сущности её можно прочитать», «нельзя создать с пустым именем».
  • Важно разделять:

  • Контракт: формат и структура (поля, типы, обязательность, коды ошибок).
  • Бизнес‑ожидания: что эти поля и коды означают.
  • 2) REST Assured: базовая конструкция given/when/then

    REST Assured — библиотека, которая делает HTTP‑запросы и позволяет писать проверки в читаемом стиле.

    Идея:

  • given() — подготовка запроса (baseUrl, параметры, headers, auth, body).
  • when() — действие (GET/POST/PUT/DELETE).
  • then() — проверки (status, headers, body).
  • 3) Параметры, тело запроса и авторизация

    Query и Path параметры

    JSON‑тело (как строка)

    Подходит для простых запросов, но усложняет поддержку при росте JSON.

    Auth

    Часто встречается Bearer‑токен:

    Практика: получение токена лучше вынести в отдельный клиент/провайдер, чтобы не размазывать по тестам (идея инкапсуляции обсуждалась в статье про Java/ООП минимум).

    4) JSON в ответе: JsonPath и точечные проверки

    REST Assured позволяет проверять поля через пути:

    Если нужно извлечь данные (например, id) и использовать дальше:

    Практика: извлечение удобно для сценариев «создал → проверил», но следите, чтобы тест оставался изолированным и сам чистил за собой тестовые данные (иначе регресс со временем начинает падать из‑за мусора).

    5) POJO вместо строк: сериализация/десериализация

    Когда JSON сложный, удобнее описать модель (POJO) и отправлять/читать объектами. Обычно используется Jackson (часто уже приходит транзитивно).

    Отправка объекта как JSON:

    Десериализация ответа:

    Практика: POJO повышают читаемость и уменьшают «строковую магию», но требуют дисциплины в моделях (названия полей, nullable‑поля, вложенные структуры).

    6) Проверки контрактов: как сделать их «защитными»

    Контрактные проверки должны ловить:

  • Ломающие изменения: пропало поле, изменился тип (id был числом → стал строкой), поменялся статус.
  • Нарушения договорённостей: неверный Content-Type, отсутствует обязательный header.
  • Типовые практики:

  • ResponseSpecification: собрать базовые ожидания (например, statusCode/contentType) и переиспользовать.
  • JSON Schema: валидировать структуру ответа по JSON‑схеме (в REST Assured есть модуль json-schema-validator). Это удобно для «форма важнее значений».
  • Негативные контракты: проверять формат ошибок.
  • Пример проверки ошибки:

    7) Стабильность API‑тестов: частые источники флаков

  • Данные: тесты создают сущности и не удаляют их.
  • Зависимость от порядка: тест A должен выполниться перед тестом B.
  • Окружение: нестабильные стенды, разные конфиги.
  • Практика: делайте тесты идемпотентными (где возможно), чистите тестовые данные и логируйте запрос/ответ при падении (REST Assured умеет log().all() или логирование только при ошибках).

    ---

    Задания для закрепления

  • Какие 4 группы проверок чаще всего делает API‑тест (минимальный набор)?
  • В чём разница между проверкой контракта и проверкой бизнес‑ожидания? Приведите по одному примеру.
  • Напишите (псевдо)пример REST Assured проверки: GET /health возвращает 200 и application/json.
  • Зачем использовать POJO вместо строкового JSON? Назовите 2 причины.
  • Как извлечь id из ответа REST Assured и зачем это может понадобиться?
  • Назовите 3 типичные причины «флаков» в API‑тестах и по одной практике против каждой.
  • <details> <summary> Ответы </summary>

    1) Минимальный набор: статус, заголовки (в т.ч. Content-Type), тело ответа (структура/поля/значения), бизнес‑правила (смысл результата).

    2) Контракт: «ответ содержит поле id типа number и Content-Type: application/json». Бизнес‑ожидание: «после создания пользователя его роль по умолчанию = USER».

    3) Пример:

    4) Две причины: (а) меньше ошибок и строкового шума в больших JSON, (б) легче переиспользовать модели и поддерживать изменения (компилятор помогает).

    5) Пример:

    Нужно для сценариев «создать → прочитать/обновить/удалить» и для подготовки данных внутри теста.

    6) Примеры:

  • Мусорные данные: чистка данных после теста или уникальные тестовые идентификаторы.
  • Зависимость от порядка: каждый тест сам создаёт предпосылки.
  • Нестабильный стенд/конфиг: фиксировать baseUrl/настройки через конфиг, логировать запрос/ответ при падении.
  • </details>

    6. Отчеты и качество: Allure, логирование и стабилизация

    Отчеты и качество: Allure, логирование и стабилизация

    Автотесты ценны не только тем, что «зелёные/красные», а тем, что быстро объясняют, что именно сломалось и почему. В реальности скорость реакции команды на падения зависит от трёх вещей:

  • Качества отчёта (что видно по тесту).
  • Качества логов (что происходило вокруг).
  • Стабильности тестов (насколько падение похоже на реальный дефект).
  • 1) Отчеты Allure: зачем и что важно

    Allure — это отчётность, которая превращает результаты JUnit в удобную «историю» выполнения:

  • Список тестов, статусы, длительность.
  • Шаги (steps), чтобы читать сценарий как последовательность действий.
  • Вложения (attachments): скриншоты, HTML, JSON запрос/ответ, логи.
  • Метки (labels): severity, epic/feature/story, owner, tags.
  • Главная цель: падение должно быть диагностируемо без локального воспроизведения (или хотя бы с минимальными усилиями).

    Подключение (Maven/Gradle) — на уровне идеи

    В проект добавляют:

  • Зависимости Allure для JUnit 5.
  • Плагин/задачу генерации отчёта.
  • Сами Maven/Gradle и JUnit 5 уже разбирались ранее — здесь важно помнить: Allure «слушает» тестовый раннер и собирает результаты в папку, из которой строится HTML-отчёт.

    Шаги: делайте их «по смыслу», а не «по коду»

    Хороший step отвечает на вопрос «что делали»:

  • «Открыть страницу логина»
  • «Войти пользователем role=ADMIN»
  • «Проверить, что отображается имя в шапке»
  • Плохой step — «clickButton», «sendKeys», «findElement». Эти технические детали лучше оставлять внутри Page Object/клиента (про Page Object см. статью про UI).

    В Allure шаги можно оформлять:

  • Аннотациями (@Step на методах-хелперах).
  • Лямбда-степами в тесте (когда нужно обернуть один конкретный участок).
  • Вложения: прикладывайте только то, что реально поможет

    Типовые полезные attachments:

  • Для UI: скриншот на падении, HTML страницы.
  • Для API: request/response (или хотя бы response body), статус, заголовки.
  • Для любых тестов: «контекст» (baseUrl, env, пользователь/роль, тестовые ID).
  • Практика: вложения особенно эффективны, если добавляются условно — например, «только при падении», чтобы не раздувать отчёт.

    Метки: удобство фильтрации и ответственности

    Метки помогают не тонуть в сотнях тестов:

  • @Tag (JUnit) — для наборов (smoke/regression). Allure их отображает.
  • @Severity — насколько критично падение.
  • @Owner — кому обычно прилетает разбор.
  • Важно: метки работают, только если команда реально ими пользуется (например, фильтрует отчёт по smoke и смотрит только critical/high).

    2) Логирование: как сделать падение «расследуемым»

    Логи нужны не «чтобы было», а чтобы ответить:

  • Какие данные использовались?
  • Какой запрос ушёл и что вернулось?
  • Какие ожидания/проверки выполнялись?
  • Где именно сломалось (контекст ошибки)?
  • Практичные правила логов в автотестах

  • Используйте уровни:
  • 1. INFO — ключевые шаги и параметры (без секретов). 2. DEBUG — подробности (например, большие JSON) для локальной диагностики. 3. ERROR — только когда тест действительно падает или инфраструктура ломается.
  • Логируйте «границы»: вход/выход из клиента API, важные действия UI, создание тестовых данных.
  • Маскируйте секреты: токены, пароли, персональные данные.
  • Делайте логи структурируемыми: одинаковые сообщения, единый формат.
  • Мини-визуализация «хорошего» сообщения:

    Связка логов и Allure

    На практике удобно:

  • При падении теста прикладывать лог (или последние N строк) как attachment.
  • Для API прикладывать request/response как отдельные вложения, а в логах оставлять короткое резюме.
  • Это даёт быстрый путь: Allure показывает шаги, вложения дают доказательства, логи дают контекст.

    3) Стабилизация: как уменьшать «флаки»

    Флак — это тест, который «то падает, то проходит» без изменений в продукте.

    Сначала классифицируйте падение

  • Проблема теста (синхронизация, ожидания, хрупкие локаторы, неочищенные данные).
  • Проблема окружения (нестабильный стенд, сеть, лимиты, зависимые сервисы).
  • Реальный дефект (воспроизводится стабильно и/или подтверждается логикой).
  • Без классификации часто появляется вредная привычка: «давайте просто добавим retry». Это маскирует дефекты и раздувает время прогона.

    Типовые причины нестабильности и что делать

  • Синхронизация UI
  • 1. Используйте ожидания состояния, а не задержки (см. статью про waits в UI). 2. Ждите исчезновение лоадеров/оверлеев, готовность кнопок, изменение URL.
  • Тестовые данные
  • 1. Генерируйте уникальные значения (суффикс по времени/рандому). 2. Чистите созданные сущности (или создавайте «в рамках теста» и удаляйте в конце).
  • Зависимость тестов друг от друга
  • 1. Каждый тест готовит предпосылки сам. 2. Не полагайтесь на порядок выполнения.
  • Нестабильное окружение
  • 1. Фиксируйте конфиг (baseUrl, таймауты) через один источник. 2. Добавляйте диагностику: пинг/health-check перед набором, логирование версий.

    Retry и quarantine — только как управляемый инструмент

    Иногда retry оправдан (например, редкий сетевой сбой), но тогда:

  • Ограничьте количество повторов.
  • Логируйте факт повтора (чтобы видеть масштаб проблемы).
  • Заведите «карантин» (отдельный тег/набор) для нестабильных тестов и план исправления.
  • Идея: стабильность — это качество продукта тестов, а не «фича» отчёта.

    ---

    Задания для закрепления

  • Назовите 4 типа информации, которые должны быть в хорошем Allure-отчёте для падения UI-теста.
  • Придумайте 5 осмысленных названий шагов (steps) для сценария «логин → переход в профиль → проверка email».
  • Какие данные нельзя логировать в явном виде? Приведите 4 примера.
  • У вас флакает API-тест «создание пользователя»: иногда приходит 409 (уже существует). Назовите 2 причины и 2 способа исправления.
  • Почему «добавить retry всем тестам» — плохая стратегия? Назовите 3 последствия.
  • Придумайте минимальный набор вложений (attachments) для падения API-теста и объясните, как каждый поможет.
  • <details> <summary> Ответы </summary>

    1) Пример набора для UI-падения:

  • Шаги сценария (что делали до падения).
  • Скриншот в момент падения.
  • HTML страницы (или хотя бы DOM-снапшот).
  • Логи теста/драйвера (ключевые действия и контекст: baseUrl, пользователь, таймауты).
  • 2) Примеры шагов:

  • «Открыть страницу логина»
  • «Ввести логин пользователя type=standard»
  • «Отправить форму логина»
  • «Открыть страницу профиля»
  • «Проверить, что email совпадает с ожидаемым»
  • 3) Нельзя логировать (или нужно маскировать):

  • Пароли.
  • Bearer-токены/refresh-токены.
  • Секретные ключи (API keys, client secret).
  • Персональные данные (паспорт, карты, адрес — зависит от домена).
  • 4) Возможные причины 409:

  • Тест создаёт пользователя с неуникальным именем/email.
  • Остались данные от предыдущих прогонов (не было очистки).
  • Способы исправления:

  • Генерировать уникальные значения (например, email с суффиксом).
  • Удалять созданного пользователя в teardown / использовать отдельный тестовый неймспейс.
  • 5) Последствия глобального retry:

  • Маскируются реальные дефекты (тест «со второго раза» проходит, но проблема остаётся).
  • Увеличивается время прогонов (особенно в CI).
  • Труднее анализировать качество: статистика «зелёных» тестов становится обманчивой.
  • 6) Минимальные attachments для API-падения:

  • Request (метод, URL, headers без секретов, body) — чтобы понять, что именно отправили.
  • Response (статус, headers, body) — чтобы увидеть фактический результат и сообщение ошибки.
  • Контекст окружения (baseUrl, env, версия/branch стенда, тестовые ID) — чтобы воспроизвести и сравнить с другими прогонами.
  • </details>

    7. Запуск в CI/CD: GitHub Actions/Jenkins и тестовые окружения

    Запуск в CI/CD: GitHub Actions/Jenkins и тестовые окружения

    CI/CD — это «конвейер», который автоматически собирает проект, запускает проверки и публикует результаты. Для автотестов главное: тесты должны запускаться воспроизводимо, на понятном окружении и с понятным отчётом (про отчётность и диагностику см. статью про Allure/логирование).

    1) Где живут автотесты в пайплайне

    Типовой поток выглядит так:

    Практика: smoke запускают часто (на PR/каждый коммит), regression — по расписанию или перед релизом. Разделять наборы удобно через @Tag в JUnit 5 (это уже обсуждалось в статье про JUnit).

    2) GitHub Actions: минимальная схема для тестов

    В GitHub Actions пайплайн описывается YAML-файлом в репозитории. Ключевые элементы:

  • on — когда запускать (push, pull_request, schedule, manual).
  • jobs — набор задач.
  • runs-on — где исполнять (runner).
  • steps — шаги: checkout, настройка JDK, запуск тестов, выгрузка артефактов.
  • Минимальный пример идеи (укороченный):

    Что важно в реальности:

  • if: always() — артефакты выгружаются даже при падении.
  • Кэш зависимостей (Maven/Gradle) — сильно ускоряет прогон.
  • Для UI тестов часто нужен headless режим браузера и установка браузера/драйвера на runner.
  • 3) Jenkins: когда и почему его выбирают

    Jenkins часто встречается в компаниях из-за гибкости и контроля инфраструктуры (свои агенты, доступ к внутренним стендам).

    Ключевые понятия:

  • Job (задача) или Pipeline (пайплайн).
  • Agent — исполнитель (машина/контейнер), где запускаются тесты.
  • Jenkinsfile — пайплайн как код, хранится в репозитории.
  • Credentials — безопасное хранение секретов (токены, пароли).
  • Пример структуры стадий (идея):

    Практика:

  • Делайте параметры запуска (например, ENV=stage, SUITE=smoke|regression).
  • Публикуйте отчёты (JUnit, Allure) как часть пайплайна.
  • Следите за «грязью» на агенте: временные файлы, артефакты, остатки тест-данных.
  • 4) Секреты и конфиги: что нельзя «зашивать» в код

    Для CI критично разделять:

  • Конфигурацию окружения (baseUrl, таймауты, флаги headless).
  • Секреты (пароли, токены, API keys).
  • Правила:

  • Секреты хранятся в GitHub Secrets / Jenkins Credentials и передаются как переменные окружения.
  • В логах секреты должны быть замаскированы (см. статью про логирование).
  • Один тестовый код должен уметь работать в разных окружениях через конфиг.
  • Пример подхода к конфигу (без привязки к библиотеке):

    5) Тестовые окружения: как не превратить CI в «рандомайзер»

    Обычно есть несколько сред: dev, qa, stage (иногда отдельная prod только для мониторинговых проверок).

    Основные проблемы и практики:

  • Данные «засоряются»
  • 1. Генерируйте уникальные тестовые идентификаторы. 2. Чистите созданные сущности в teardown.
  • Тесты конфликтуют в параллели
  • 1. Не используйте общие аккаунты/корзины/заказы без изоляции. 2. Делайте неймспейсы: префиксы, отдельные тестовые проекты/организации.
  • Стенд нестабилен
  • 1. Добавьте быстрый health-check перед набором. 2. Разделяйте падения: «тест сломан» vs «окружение недоступно».

    Если команда использует ephemeral environments (временные стенды на ветку), важна договорённость: кто создаёт стенд, как получить baseUrl, кто и когда удаляет.

    6) Скорость и стабильность в CI

  • Выборочный запуск: smoke на PR, полный регресс — реже.
  • Параллельность: делите тесты на группы (по тегам/пакетам) и запускайте в нескольких job/stage.
  • Retry — только управляемо: ограниченно, с логированием факта повторов и «карантином» для флаков.
  • Артефакты диагностики: отчёт, логи, для UI — скриншоты/HTML, для API — request/response (см. статью про Allure).
  • ---

    Задания для закрепления

    1) Нарисуйте (текстом) простой пайплайн для PR: сборка → smoke → публикация отчёта.

    2) Какие 3 вещи вы обязаны вынести из кода в конфигурацию/секреты для CI? Приведите примеры.

    3) Почему важно выгружать артефакты «даже если тесты упали»? Назовите 2 причины.

    4) У вас флакают UI-тесты только в CI, локально всё стабильно. Назовите 3 вероятные причины именно CI-специфики.

    5) Придумайте стратегию: какие наборы тестов запускать на pull_request, на push в main и по расписанию ночью.

    <details> <summary>

    Ответы

    </summary>

    1)

    2) Примеры:

  • BASE_URL (dev/qa/stage).
  • Учётные данные тестового пользователя (логин/пароль) или токен.
  • Таймауты/флаги запуска (например, HEADLESS=true, BROWSER=chrome).
  • 3) Причины:

  • Диагностика: без отчёта/логов/скриншотов трудно понять, что сломалось.
  • Сравнение прогонов: артефакты помогают увидеть паттерн (время падения, окружение, ответ сервиса).
  • 4) Возможные CI-специфичные причины:

  • Headless/разрешение экрана/шрифты отличаются от локальных.
  • Ресурсы runner/агента слабее: тайминги, медленнее рендер/сеть.
  • Параллельные прогоны конфликтуют по данным (общие пользователи/сущности).
  • 5) Пример стратегии:

  • pull_request: только smoke + быстрые API/юнит проверки.
  • push в main: smoke + расширенный набор критичных регресс-проверок.
  • Ночью по расписанию: полный regression (возможно, с разбивкой на параллельные джобы) и отчёт с трендами.
  • </details>