Автоматизированное тестирование на Python: Путь от Junior до Senior

Интенсивный трехмесячный курс с упором на практику (70%), охватывающий современные инструменты (pytest, Playwright, Docker) и архитектурные паттерны. Программа включает создание полноценного фреймворка, настройку CI/CD и подготовку портфолио для реальной работы.

1. Основы тестирования: пирамида, жизненный цикл и виды тестов

Основы тестирования: пирамида, жизненный цикл и виды тестов

Добро пожаловать на курс «Автоматизированное тестирование на Python: Путь от Junior до Senior». Мы начинаем с фундамента. Прежде чем писать код на pytest или разворачивать Docker-контейнеры, необходимо разобраться в архитектуре качества. Без понимания того, что и зачем мы тестируем, даже самый сложный код будет бесполезен.

В этом модуле мы разберем теорию, которая отличает инженера по автоматизации от простого «писателя скриптов», и напишем первые тесты на чистом Python.

Цель модуля

К концу этого занятия вы будете:

* Понимать концепцию Пирамиды тестирования и избегать антипаттернов. * Знать этапы Жизненного цикла тестирования (STLC). * Различать виды тестов: Unit, Integration, E2E, Smoke, Regression. * Уметь применять паттерн AAA (Arrange-Act-Assert) на практике.

---

Теория

1. Пирамида тестирования (Testing Pyramid)

Это ключевая ментальная модель для QA-инженера. Она определяет стратегию автоматизации: сколько и каких тестов нужно писать.

!Классическая пирамида тестирования Майка Кона

#### Уровни пирамиды (снизу вверх):

  • Unit Tests (Модульные тесты)
  • * Суть: Проверка наименьшей единицы кода (функции, метода) в полной изоляции. Никаких баз данных, сети или файловой системы. * Скорость: Мгновенная (миллисекунды). * Количество: 70-80% от всех тестов. * Инструменты: unittest, pytest.

  • Integration Tests (Интеграционные тесты)
  • * Суть: Проверка взаимодействия между компонентами (например, сервис + база данных, или сервис A + сервис B). * Скорость: Средняя (секунды). * Количество: 15-20%. * Инструменты: pytest, requests, Docker.

  • E2E / UI Tests (Сквозные тесты)
  • * Суть: Эмуляция действий реального пользователя через интерфейс. Проверяем систему целиком. * Скорость: Медленная (минуты). * Количество: 5-10% (только критические сценарии). * Инструменты: Selenium, Playwright.

    > Антипаттерн «Рожок мороженого» (Ice Cream Cone): Ситуация, когда проект перегружен медленными UI-тестами при отсутствии Unit-тестов. Это ведет к нестабильности (flaky tests) и долгому ожиданию результатов.

    2. Жизненный цикл тестирования (STLC)

    Автоматизация не существует в вакууме. Она встраивается в процесс STLC (Software Testing Life Cycle):

  • Requirement Analysis: Понимаем, что тестировать. Если требование нелогично, писать автотест рано.
  • Test Planning: Выбираем инструменты (Python + pytest) и стратегию.
  • Test Case Design: Пишем сценарии. Важно: хороший автотест рождается из хорошего ручного тест-кейса.
  • Environment Setup: Настройка тестовой среды (БД, тестовые данные).
  • Execution: Запуск тестов (CI/CD).
  • Closure: Анализ отчетов и покрытия кода.
  • 3. Виды тестов по цели запуска

    * Smoke Testing (Дымовое): Быстрая проверка критического функционала («Запускается ли приложение?», «Работает ли логин?»). Если дым идет — глубокое тестирование не имеет смысла. * Regression Testing (Регрессионное): Полный прогон тестов после внесения изменений в код. Гарантия того, что новые фичи не сломали старые. Это главная задача автотестов.

    4. Паттерн AAA (Arrange-Act-Assert)

    Золотой стандарт структуры теста. Если ваши тесты не следуют этому паттерну, их будет сложно читать и поддерживать.

  • Arrange (Подготовка): Создаем объекты, настраиваем моки, готовим данные.
  • Act (Действие): Вызываем тестируемый метод.
  • Assert (Проверка): Сравниваем фактический результат с ожидаемым.
  • Полезные ссылки

    * The Practical Test Pyramid (Martin Fowler) — библия для автоматизатора. * Software Testing Life Cycle — подробнее про этапы.

    ---

    Практика

    В этом модуле мы используем чистый Python и оператор assert, чтобы понять механику проверок без «магии» фреймворков.

    Задача 1: Чистая функция (Unit-тест)

    Дано: Функция расчета скидки. Задача: Написать тест, проверяющий корректность расчета и валидацию входных данных.

    Задача 2: Тестирование состояния (Stateful Testing)

    Дано: Класс User, который может менять статус. Задача: Проверить переход состояния.

    Задача 3: Ручной Mock (Интеграционный тест)

    Дано: Сервис отправки уведомлений, зависящий от внешнего провайдера (Email). Задача: Протестировать логику сервиса, подменив реальную отправку писем «заглушкой» (Mock), чтобы не спамить реальными письмами.

    ---

    Домашнее задание

    Контекст: Вы разрабатываете систему для интернет-магазина. Есть класс Cart (Корзина), который позволяет добавлять товары (словарь {'name': str, 'price': int}), удалять их и считать общую сумму.

    Задание:

  • Реализуйте класс Cart с методами add_item(item), remove_item(item_name), calculate_total().
  • Напишите 3 теста (функции), используя assert и паттерн AAA:
  • * Тест добавления товара (проверить, что товар появился в списке и сумма изменилась). * Тест удаления товара. * Тест пустой корзины (сумма должна быть 0).
  • Критерии приемки:
  • * Код запускается без ошибок. * При изменении логики calculate_total (например, добавить +1 к сумме) тесты должны падать с ошибкой AssertionError. * В каждом тесте есть комментарии # Arrange, # Act, # Assert.

    ---

    Типичные ошибки новичков

  • «Немые» ассерты:
  • Плохо:* assert result == 5 (Если упадет, вы увидите просто AssertionError). Хорошо:* assert result == 5, f"Expected 5, but got {result}" (Сразу понятно, что пошло не так).
  • Зависимые тесты: Один тест меняет глобальную переменную, а второй падает из-за этого. Каждый тест должен быть изолирован (создавать свои данные заново).
  • Тестирование всего сразу: Один тест проверяет и логин, и корзину, и оплату. Если он упадет, непонятно, где ошибка. Следуйте правилу: Один тест — одна проверка.
  • Чек-лист прогресса

    * [ ] Я понимаю разницу между Unit и E2E тестами. * [ ] Я знаю, что такое AAA и использую его в коде. * [ ] Я написал тест с использованием assert и кастомным сообщением об ошибке. * [ ] Я понимаю, почему нельзя писать только UI-тесты (пирамида).

    10. Сложные сценарии: базы данных, OAuth и вебсокеты

    Сложные сценарии: базы данных, OAuth и вебсокеты

    Добро пожаловать на десятую неделю курса «Автоматизированное тестирование на Python». Мы уже построили надежный фундамент: умеем писать UI и API тесты, запускать их в Docker и CI/CD. Но реальный мир Enterprise-разработки сложнее, чем тестирование статичных страниц.

    Что отличает Senior QA Automation инженера от Junior? Умение работать с «черным ящиком» изнутри. Senior не просто кликает по кнопкам, он подготавливает состояние системы через базу данных, обходит сложные механизмы авторизации (OAuth) и умеет тестировать асинхронные интерфейсы реального времени (WebSockets).

    В этом модуле мы разберем три «страшных» зверя, которых боятся новички, и научимся их приручать.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Подключаться к SQL-базам данных (PostgreSQL/MySQL) напрямую из тестов для подготовки и проверки данных. * Использовать SQLAlchemy для удобной работы с БД в Python. * Понимать поток OAuth 2.0 и авторизовываться в тестах программно, минуя UI. * Тестировать WebSockets с использованием асинхронности (async/await).

    Часть 1: Работа с Базой Данных (SQLAlchemy)

    Теория: Зачем тестам доступ к БД?

    В пирамиде тестирования мы часто говорим об изоляции. Но E2E и интеграционные тесты требуют данных. Есть два пути:

  • Через UI/API: Создать пользователя через POST-запрос, потом проверить его создание GET-запросом. Это медленно и ненадежно (если API создания сломано, тест проверки тоже упадет).
  • Через БД: Напрямую вставить запись в таблицу users перед тестом и проверить запись в таблице orders после теста. Это быстро, надежно и дает полный контроль над состоянием.
  • Для работы с БД в Python стандартом является библиотека SQLAlchemy. Она позволяет работать с базой как через «сырой» SQL, так и через ORM (Object-Relational Mapping), где таблицы представлены в виде классов Python.

    !Схема прямого взаимодействия тестов с базой данных для подготовки и проверки состояния

    Практика: Настройка фикстуры БД

    Установим библиотеку:

    Предположим, у нас есть база PostgreSQL. Напишем фикстуру, которая создает соединение перед всей сессией тестов и закрывает его после.

    В файле conftest.py:

    Важнейший паттерн: Обратите внимание на transaction.rollback(). Это позволяет не заботиться об удалении тестовых данных. Мы что-то записали, проверили, а потом «отменили» все изменения, как будто их и не было.

    Пример теста с проверкой в БД

    Часть 2: Авторизация через OAuth 2.0

    Теория: Почему UI-логин — это зло

    Многие начинающие автоматизаторы в каждом тесте пишут:

  • Открыть страницу логина.
  • Ввести логин/пароль.
  • Нажать «Войти».
  • Ждать редиректа.
  • Если у вас 100 тестов, и логин занимает 5 секунд, вы тратите 8 минут просто на вход в систему. Более того, если вы используете Google или Facebook для входа (OAuth), капча или двухфакторная аутентификация (2FA) заблокируют вашего робота.

    OAuth 2.0 — это протокол, позволяющий получить доступ к ресурсам без передачи пароля напрямую сервису. В тестах мы должны использовать программный логин (Programmatic Login).

    !Упрощенная схема получения токена доступа (Access Token) для тестов

    Практика: Получение токена и инъекция

    Вместо кликов мы отправляем один API-запрос к серверу авторизации, получаем токен и подкладываем его в браузер или API-клиент.

    Пример фикстуры для получения токена (Keycloak/Auth0/Google):

    #### Использование в API-тестах

    #### Использование в UI-тестах (Playwright)

    В Playwright мы можем установить токен прямо в localStorage или cookies браузера, пропуская экран логина.

    Часть 3: Тестирование WebSockets

    Теория: HTTP vs WebSocket

    HTTP — это рация: нажал кнопку (Request), сказал, отпустил, ждешь ответа (Response). Связь прерывается. WebSocket — это телефонный звонок: соединение установлено постоянно, обе стороны могут говорить и слушать одновременно в любой момент.

    Тестирование вебсокетов требует асинхронного подхода, так как мы не знаем, когда именно сервер пришлет сообщение.

    !Визуальное различие между дискретным HTTP и постоянным WebSocket соединением

    Практика: Библиотека websockets

    Для тестов нам понадобятся pytest-asyncio и библиотека websockets.

    В pytest.ini нужно разрешить асинхронные тесты:

    Пример теста эхо-сервера (сервер возвращает то, что ему отправили):

    Сложный сценарий: Broadcast (Чат)

    Как проверить, что сообщение от Юзера А пришло Юзеру Б?

    Домашнее задание

    Контекст: Вы тестируете систему уведомлений. У вас есть доступ к БД (PostgreSQL), API авторизации и WebSocket-серверу уведомлений.

    Задание:

  • Напишите фикстуру db_connection, которая подключается к БД (можно использовать SQLite для простоты или поднять Postgres в Docker).
  • Напишите фикстуру fake_token, которая генерирует JWT-токен (можно просто строку, если нет реального сервера).
  • Реализуйте интеграционный тест:
  • * Arrange: Вставьте в БД запись о новом пользователе через SQL. * Act: Подключитесь к WebSocket от имени этого пользователя (передав токен в заголовках или URL). * Act 2: Сделайте HTTP POST запрос к API /trigger-notification, который должен отправить уведомление этому пользователю. * Assert: Проверьте через WebSocket, что уведомление пришло.

    Критерии приемки: * Используется sqlalchemy. * Используется pytest-asyncio и websockets. * Тест атомарен: данные удаляются после выполнения (через rollback или фикстуру очистки).

    Типичные ошибки

  • «Грязная» база: Тесты меняют данные в БД, но не убирают за собой. Следующий запуск падает, потому что «пользователь с таким email уже существует». Решение: используйте транзакции и rollback или удаляйте данные в teardown.
  • Хардкод токенов: Использование токена, который вы скопировали из браузера неделю назад. Токены протухают. Решение: получайте свежий токен в фикстуре перед запуском тестов.
  • Race Conditions в WebSocket: Вы пытаетесь прочитать сообщение (recv()) до того, как сервер успел его отправить. Решение: используйте механизмы повторных попыток (retries) или увеличенные таймауты, но не time.sleep().
  • Смешивание синхронного и асинхронного кода: Попытка вызвать requests.get внутри async def функции заблокирует весь цикл событий. Решение: используйте асинхронные HTTP-клиенты (например, httpx) внутри асинхронных тестов.
  • В следующем модуле мы займемся метриками и аналитикой: узнаем, что такое Code Coverage, как бороться с «мигающими» (flaky) тестами и как проходить собеседования на позицию Senior QA.

    11. Метрики качества: Coverage.py и анализ flaky-тестов

    Метрики качества: Coverage.py и анализ flaky-тестов

    Добро пожаловать на одиннадцатую неделю курса «Автоматизированное тестирование на Python». Мы уже научились писать тесты, запускать их в Docker и настроили CI/CD пайплайны. Казалось бы, работа сделана: тесты зеленые, деплой идет. Но как ответить на вопрос менеджера: «Насколько хорошо протестирован наш продукт?» или «Почему билд упал, а после перезапуска прошел?».

    Отличие Senior-инженера от Junior не только в умении писать код, но и в умении измерять его эффективность и обеспечивать стабильность. Сегодня мы поговорим о метриках: как узнать, какой код мы забыли протестировать (Code Coverage), и как бороться с главным врагом автоматизации — нестабильными (flaky) тестами.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Измерять покрытие кода тестами с помощью Coverage.py и плагина pytest-cov. * Понимать разницу между строковым покрытием (Line Coverage) и покрытием ветвлений (Branch Coverage). * Анализировать отчеты и исключать лишние файлы из статистики. * Выявлять и классифицировать flaky-тесты. * Использовать инструменты для отладки нестабильности: pytest-repeat, pytest-rerunfailures.

    Часть 1: Покрытие кода (Code Coverage)

    Теория: Что мы измеряем?

    Code Coverage (покрытие кода) — это метрика, которая показывает, какая часть исходного кода была выполнена во время запуска тестов. Обычно она выражается в процентах.

    !Визуализация концепции покрытия кода: зеленым отмечены проверенные строки, красным — пропущенные

    Существует несколько уровней покрытия:

  • Line Coverage (Покрытие строк): Самая простая метрика. Если строка кода была выполнена хотя бы раз — она считается покрытой.
  • Branch Coverage (Покрытие ветвлений): Более строгая метрика. Она проверяет, были ли выполнены все возможные переходы в условиях if/else. Например, если у вас есть if x > 0, строковое покрытие засчитает тест с x=5, но покрытие ветвлений потребует еще и тест с x=-5, чтобы зайти в ветку else (даже если она неявная).
  • > «100% покрытие кода не гарантирует отсутствие багов, но 0% покрытия гарантирует, что вы ничего не проверили». — Народная мудрость QA

    Важно: Высокий процент покрытия говорит лишь о том, что код запускался. Это не значит, что он работает правильно. Вы можете выполнить функцию, но забыть написать assert — покрытие будет 100%, а пользы — ноль.

    Практика: Настройка Coverage.py

    В экосистеме Python стандартом является библиотека coverage.py. Для удобной интеграции с pytest мы будем использовать плагин pytest-cov.

    Установка:

    #### Базовый запуск

    Допустим, у нас есть файл calculator.py и тесты к нему. Чтобы замерить покрытие, добавим флаг --cov:

    Вывод в консоли будет выглядеть примерно так:

    #### Генерация HTML-отчета

    Консоль удобна для быстрой проверки, но для глубокого анализа нужен HTML-отчет, который покажет конкретные пропущенные строки.

    После выполнения в папке проекта появится директория htmlcov. Откройте файл htmlcov/index.html в браузере. Вы увидите список файлов. Кликнув на файл, вы увидите исходный код, где красным цветом выделены строки, в которые тесты «не заходили».

    #### Конфигурация (.coveragerc)

    В реальных проектах мы не хотим считать покрытие для самих тестов, файлов миграций или конфигурационных файлов. Для этого создают файл настройки .coveragerc (или секцию в pyproject.toml / setup.cfg).

    Создайте файл .coveragerc в корне проекта:

    Теперь при запуске pytest --cov настройки подтянутся автоматически.

    Branch Coverage в действии

    Рассмотрим пример, почему важно включать branch = True.

    Тест:

    * Line Coverage: 100%. Все строки функции выполнены. * Branch Coverage: 50%. Мы проверили ветку True, но не проверили случай, когда условие ложно (number <= 0).

    Включение branch = True в конфиге подсветит строку с if желтым цветом, указывая на неполное покрытие ветвлений.

    Часть 2: Flaky-тесты (Мигающие тесты)

    Теория: Кошмар автоматизатора

    Flaky-тест (мигающий, хрупкий) — это тест, который при одних и тех же исходных данных (код не менялся) то проходит, то падает.

    !Иллюстрация нестабильности: тест падает случайно без изменений в коде

    Почему это плохо?

  • Потеря доверия: Разработчики перестают реагировать на красные билды. «А, это опять тот тест упал, просто перезапусти». В итоге реальные баги пропускаются.
  • Замедление поставки: Приходится перезапускать пайплайны, теряя время (иногда часы).
  • Основные причины нестабильности

  • Race Conditions (Состояние гонки): Тест зависит от порядка выполнения или скорости работы системы (особенно в UI и Async тестах).
  • Тестовые данные: Использование одних и тех же данных в параллельных тестах или отсутствие очистки БД.
  • Внешние зависимости: API стороннего сервиса недоступен или отвечает медленно.
  • Время и даты: Тест падает только по ночам или в високосный год.
  • Порядок тестов: Тест А меняет глобальную переменную, а Тест Б ожидает её дефолтное значение.
  • Практика: Охота на Flaky-тесты

    #### 1. Выявление (pytest-repeat)

    Если тест упал один раз, но проходит локально, попробуйте запустить его 100 раз подряд. Плагин pytest-repeat поможет в этом.

    Запуск конкретного теста 50 раз:

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

    #### 2. Изоляция (pytest-randomly)

    Если тест падает только при запуске всего сьюта, но проходит изолированно, значит, он зависит от другого теста (загрязнение состояния). Плагин pytest-randomly перемешивает порядок выполнения тестов.

    Запустите тесты. Если упадет — посмотрите в лог, какой seed использовался, и вы сможете воспроизвести этот порядок снова:

    #### 3. Лечение симптомов (pytest-rerunfailures)

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

    В pytest.ini:

    Это означает: «Если тест упал, подожди 1 секунду и попробуй еще раз. И так до 2 раз». Если с 3-й попытки прошел — тест помечается как PASSED (но в логах будет видно, что были ретраи).

    Внимание: Не используйте это для Unit-тестов! Юнит-тесты должны быть железобетонно стабильными. Ретраи — это «пластырь» для UI/E2E.

    Часть 3: Анализ результатов

    Senior QA не просто смотрит на «зеленый» Jenkins. Он анализирует тренды.

    Allure History

    Если вы настроили Allure (как в модуле 6), используйте вкладку Trend. Если вы видите, что график длительности тестов ползет вверх, или количество broken тестов растет — это сигнал к рефакторингу.

    Code Coverage как Quality Gate

    В CI/CD можно настроить порог покрытия. Если PR снижает покрытие ниже 80% — билд падает.

    Пример для GitHub Actions:

    Флаг --cov-fail-under=80 заставит pytest вернуть код ошибки, если покрытие меньше 80%.

    Полезные ресурсы

    * Документация Coverage.py * Документация pytest-cov * Google Testing Blog: Flaky Tests

    ---

    Домашнее задание

    Контекст: Вы работаете над проектом интернет-магазина. У вас есть набор тестов, но вы не знаете, насколько они эффективны.

    Задача 1: Настройка Coverage

  • Установите pytest-cov.
  • Создайте файл .coveragerc, исключив из отчета папку tests/ и любые файлы __init__.py.
  • Запустите тесты с генерацией HTML-отчета.
  • Найдите в коде функцию (или ветку if/else), которая не покрыта тестами (красная зона).
  • Напишите тест, который закрывает эту «дыру».
  • Задача 2: Детектив Flaky-теста Создайте файл test_flaky.py с заведомо нестабильным тестом:

  • Запустите этот тест 50 раз с помощью pytest-repeat и убедитесь, что он падает.
  • Настройте pytest-rerunfailures так, чтобы тест проходил (зеленый статус), несмотря на сбои.
  • Добавьте маркер @pytest.mark.flaky(reruns=5) к этому тесту, чтобы переопределить глобальные настройки.
  • Критерии приемки: * HTML-отчет генерируется и открывается. * Процент покрытия вырос после добавления нового теста. * Нестабильный тест проходит в CI благодаря ретраям.

    Типичные ошибки новичков

  • Гонка за 100%: Пытаться покрыть тестами каждую строчку, включая print() или тривиальные геттеры. Это трата времени. Ориентируйтесь на 80-90% для бизнес-логики.
  • Игнорирование .coveragerc: В отчет попадают библиотеки из venv или сами тесты, искажая статистику.
  • time.sleep() как решение: Если тест падает из-за скорости, новички ставят sleep(5). Это делает тесты медленными. Используйте явные ожидания (Waiters) или ретраи.
  • Ретраи для всего: Включение автоматического перезапуска для всех тестов скрывает реальные баги в коде приложения. Используйте их точечно.
  • 12. Финал: архитектура фреймворка, портфолио и собеседование

    Финал: архитектура фреймворка, портфолио и собеседование

    Поздравляем! Вы прошли долгий путь длиной в 12 недель. Вы начали с простых утверждений assert, а закончили настройкой CI/CD пайплайнов и контейнеризацией. Вы больше не новичок, который пишет скрипты «на коленке». Вы обладаете инструментарием инженера по автоматизации.

    Однако знание инструментов — это только половина успеха. Вторая половина — умение объединить их в единую, масштабируемую систему (фреймворк) и способность продать свои навыки работодателю.

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

    Архитектура тестового фреймворка

    Чем отличается набор скриптов от фреймворка? Скрипты решают сиюминутные задачи. Фреймворк создает структуру, которая позволяет добавлять новые тесты быстро, дешево и без дублирования кода. Хороший фреймворк похож на конструктор LEGO: у вас есть стандартные блоки, из которых вы собираете сценарии.

    !Диаграмма слоев архитектуры автоматизированного тестирования

    Основные слои

  • Config Layer (Конфигурация):
  • Никакого хардкода. URL стендов, логины, пароли и таймауты должны лежать в переменных окружения (.env) или файлах конфигурации. Используйте библиотеку pydantic для валидации настроек.

  • Core / Client Layer (Ядро):
  • Обертки над библиотеками. Если вы используете requests, создайте базовый класс BaseClient, который умеет логировать все запросы и ответы, автоматически подставлять базовый URL и обрабатывать таймауты.

  • Domain Layer (Бизнес-логика):
  • Здесь живут Page Objects (для UI) и API Steps (для бэкенда). Тест не должен знать, какой локатор у кнопки «Купить». Тест должен вызывать метод cart_page.click_buy().

  • Test Layer (Тесты):
  • Сами функции с префиксом test_. Они должны быть декларативными. Идеальный тест читается как сценарий на английском языке, без технических деталей реализации.

    Пример структуры проекта

    Вот как должен выглядеть ваш итоговый проект:

    Итоговые проекты курса

    Чтобы закрепить знания и наполнить портфолио, вам нужно реализовать два проекта. Это ваша дипломная работа.

    Проект 1: E2E тестирование демо-магазина (UI + API)

    Цель: Продемонстрировать умение работать с гибридным тестированием. Объект: Любой демо-сайт (например, SauceDemo, OpenCart или PetStore).

    Требования: * UI: Реализовать паттерн Page Object Model. Покрыть сценарии: логин, добавление в корзину, оформление заказа. * API: Если у сайта есть API, использовать его для подготовки данных (например, создать пользователя через API перед UI-тестом логина). * Данные: Использовать Faker для генерации email и адресов. * Отчеты: Интеграция с Allure (скриншоты при падении).

    Проект 2: Инфраструктура для публичного API

    Цель: Показать навыки DevOps и архитектуры. Объект: GitHub API, Trello API или любой публичный сервис.

    Требования: * Docker: Тесты должны запускаться внутри контейнера. * CI/CD: Настроить GitHub Actions. Пайплайн должен запускаться на Pull Request, прогонять тесты и сохранять Allure-отчет как артефакт. * Notifications: (Опционально) Отправка результатов в Telegram/Slack.

    Портфолио: GitHub как ваше резюме

    Рекрутеры и техлиды смотрят код. Пустой GitHub — это красный флаг для Junior-специалиста. Но и GitHub, заваленный мусором («hello world», «test1»), не лучше.

    Правила идеального репозитория

  • README.md — это король.
  • Никто не будет читать ваш код, если не поймет, что он делает. В README.md должно быть: * Заголовок: Что это за проект. * Стек технологий: Python, Pytest, Playwright, Docker, Allure (используйте бейджи). * Как запустить: Пошаговая инструкция (git clone -> pip install -> pytest). * Примеры отчетов: Скриншот или ссылка на Allure.

  • Чистота кода.
  • Используйте линтеры (flake8, black). Соблюдайте PEP8. Удалите закомментированный код и лишние принты.

  • История коммитов.
  • Плохо: Update file, Fix, Fix 2. Хорошо: Add login page object, Refactor api client, Setup CI pipeline.

  • .gitignore обязателен.
  • В репозитории не должно быть папок .venv, __pycache__, .idea или файлов с реальными паролями (.env).

    Подготовка к собеседованию

    Собеседование на QA Automation обычно состоит из трех частей: теория, задачи на кодинг и вопросы по инструментам.

    Топ вопросов по Python

  • Типы данных: Изменяемые и неизменяемые. В чем разница между list и tuple, и почему ключом словаря может быть только неизменяемый тип?
  • ООП: Три кита (Инкапсуляция, Наследование, Полиморфизм) на примерах из автоматизации (например, BasePage).
  • Декораторы: Как работают, зачем нужны (пример: @pytest.fixture, @allure.step).
  • Генераторы и итераторы: В чем отличие yield от return? (Критично для понимания фикстур).
  • Топ вопросов по тестированию и инструментам

  • Pytest:
  • * Что такое фикстуры и какие у них есть scope? * Как передать параметры в тест (parametrize)? * Как запустить тесты параллельно?
  • Selenium / Playwright:
  • * Что такое неявные (implicit) и явные (explicit) ожидания? Почему time.sleep — это плохо? * Как работает Page Object Model? * Как обработать StaleElementReferenceException?
  • API:
  • * Разница между POST и PUT. * Коды ответов (2xx, 3xx, 4xx, 5xx).
  • Git & CI/CD:
  • * Что такое Merge Request? * Зачем нужен Docker в тестах?

    Live Coding (Задачи на код)

    Вас попросят написать код в реальном времени. Обычно это алгоритмические задачи легкого уровня или задачи на работу со строками/списками.

    Задача:* Написать функцию, которая проверяет, является ли строка палиндромом. Задача:* Найти уникальные элементы в списке. Задача:* Написать простейший клиент для API (GET запрос и проверка JSON).

    > «Не молчите во время лайв-кодинга. Рассуждайте вслух. Интервьюеру важнее понять ход ваших мыслей, чем увидеть идеально работающий код с первой попытки».

    План обучения: 3 месяца (Сводная таблица)

    Ниже представлен полный план курса, который вы прошли. Используйте его как чек-лист для повторения перед собеседованием.

    | Неделя | Темы | Часы (Теория / Практика) | Ключевые задачи | Ресурсы | | :--- | :--- | :--- | :--- | :--- | | 1 | Основы тестирования<br>Пирамида, STLC, виды тестов. | 2 / 4 | Написание тест-кейсов. Ручное тестирование формы. | ISTQB Glossary | | 2 | Погружение в pytest<br>Asserts, fixtures, parametrize, marks. | 3 / 7 | Рефакторинг тестов с фикстурами. Параметризация калькулятора. | Pytest Docs | | 3 | API-тестирование<br>Requests, REST, JSON Schema, Mocking. | 3 / 8 | CRUD тесты для PetStore. Валидация схем. | Requests Docs | | 4 | UI-тестирование<br>Playwright, Locators, POM. | 4 / 10 | Автоматизация логина и корзины. Реализация Page Objects. | Playwright Python | | 5 | Тестовые данные<br>Faker, JSON/YAML, Data Driven Testing. | 2 / 5 | Генератор пользователей. Вынос конфигов в YAML. | Faker Docs | | 6 | Инфраструктура<br>Pytest.ini, Logging, Allure. | 2 / 5 | Настройка красивых отчетов. Логирование шагов. | Allure Docs | | 7 | CI/CD<br>GitHub Actions, Workflows. | 3 / 6 | Пайплайн для запуска тестов по пушу. Артефакты. | GitHub Actions | | 8 | Контейнеризация<br>Docker, Dockerfile, Compose. | 3 / 7 | Упаковка тестов в образ. Запуск через Compose. | Docker Get Started | | 9 | Продвинутый pytest<br>Xdist (параллельность), Hooks, Plugins. | 2 / 5 | Ускорение тестов в 4 потока. Написание плагина. | Pytest-xdist | | 10 | Сложные сценарии<br>DB (SQLAlchemy), OAuth, WebSockets. | 3 / 6 | Тест с проверкой в БД. Авторизация через API. | SQLAlchemy | | 11 | Метрики и качество<br>Coverage, Flaky tests. | 2 / 4 | Анализ покрытия кода. Лечение мигающих тестов. | Coverage.py | | 12 | Финал<br>Архитектура, Портфолио, Собеседование. | 2 / 10 | Итоговый проект. Оформление GitHub. | |

    Заключение

    Путь от Junior до Senior — это не спринт, а марафон. Этот курс дал вам карту и набор инструментов. Теперь всё зависит от вашей практики. Не бойтесь сложных задач, читайте чужой код, вносите вклад в Open Source и постоянно учитесь.

    Удачи на собеседованиях! Ваш код будет чистым, а тесты — зелеными.

    2. Погружение в pytest: фикстуры, параметризация и маркировка

    Погружение в pytest: фикстуры, параметризация и маркировка

    В предыдущем модуле мы научились писать простейшие тесты, используя встроенный оператор assert. Это отличный способ понять суть проверок, но в реальных проектах этого недостаточно. Представьте, что у вас 500 тестов. Как запустить только те, что касаются оплаты? Как подготовить базу данных перед каждым тестом и очистить её после? Как проверить одну функцию на 10 разных наборах данных, не копируя код?

    Здесь на сцену выходит pytest — самый популярный, мощный и гибкий фреймворк для тестирования в мире Python. Сегодня мы превратим ваши разрозненные скрипты в профессиональный тестовый сьют.

    Почему pytest?

    Хотя в стандартной библиотеке Python есть модуль unittest (наследие Java JUnit), индустрия сделала выбор в пользу pytest. Причины просты:

  • Лаконичность: Не нужно создавать классы-наследники от TestCase. Тесты — это просто функции.
  • Умные ассерты: Не нужно помнить методы self.assertEqual, self.assertTrue. Достаточно простого assert, а pytest сам покажет детальную разницу между ожидаемым и фактическим результатом.
  • Фикстуры: Революционный подход к управлению зависимостями и состоянием (setup/teardown).
  • Плагины: Огромная экосистема расширений.
  • Установка и первый запуск

    Для начала установим библиотеку:

    Pytest использует автоматическое обнаружение тестов (test discovery). Чтобы он нашел ваши тесты, соблюдайте правила именования:

    Файлы должны называться test_.py или *_test.py. * Функции тестов должны начинаться с префикса test_. * Классы тестов (если вы их используете) должны начинаться с Test.

    Пример файла test_math.py:

    Запуск из терминала:

    Фикстуры (Fixtures): Сердце pytest

    Вспомните паттерн AAA (Arrange-Act-Assert). Этап Arrange (подготовка данных) часто повторяется. Например, для тестирования корзины интернет-магазина нам нужно в каждом тесте создавать экземпляр этой корзины.

    Фикстуры — это функции, которые готовят окружение до теста и (опционально) убирают за собой после. Они позволяют отделить код инициализации от логики проверки.

    !Схематичное изображение того, как фикстура оборачивает тест: сначала выполняется код настройки, затем передается управление тесту, а после завершения теста выполняется код очистки.

    Создание простой фикстуры

    Используем декоратор @pytest.fixture.

    Обратите внимание на магию: мы передаем имя фикстуры empty_cart как аргумент в функцию теста. Pytest сам найдет фикстуру, выполнит её и передаст результат внутрь теста. Это называется Dependency Injection (Внедрение зависимостей).

    Setup и Teardown (Подготовка и Очистка)

    Часто нам нужно не только создать ресурс (открыть файл, соединение с БД, браузер), но и закрыть его после теста. Для этого вместо return используется ключевое слово yield.

    Всё, что написано до yield, выполняется перед тестом. Всё, что после — после теста (даже если тест упал с ошибкой).

    Области видимости (Scopes)

    По умолчанию фикстура создается заново для каждой функции теста (scope='function'). Но если открытие браузера занимает 5 секунд, а у нас 100 тестов, мы будем ждать вечность. Мы можем изменить время жизни фикстуры.

    * function: (по умолчанию) один раз для каждого теста. * class: один раз для класса тестов. * module: один раз для всего файла (модуля). * session: один раз за весь запуск тестов (например, глобальная настройка БД).

    Файл conftest.py

    Если вы хотите использовать фикстуру в нескольких файлах с тестами, не нужно её импортировать. Просто создайте файл conftest.py в корне папки с тестами и поместите фикстуру туда. Pytest увидит её автоматически во всем проекте.

    Параметризация: Один тест — много данных

    Допустим, нам нужно проверить функцию сложения на разных числах. Новички часто пишут цикл for внутри теста. Это плохая практика: если упадет одна итерация, вы не узнаете результаты остальных, и отчет будет неинформативным.

    Используйте декоратор @pytest.mark.parametrize.

    В этом примере pytest сгенерирует и запустит 4 независимых теста. Если упадет кейс с нулем, остальные всё равно выполнятся.

    Маркировка (Marks): Управление запуском

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

    Встроенные маркеры

    * @pytest.mark.skip(reason="Баг еще не исправлен") — пропускает тест. * @pytest.mark.xfail — помечает тест как "ожидаемо падающий". Если он упадет — это не будет считаться ошибкой (зеленый отчет). Если пройдет — будет помечен как XPASS.

    Кастомные маркеры

    Вы можете придумать свои метки, например smoke, slow, api, ui.

    Запуск только smoke-тестов:

    Важно: Чтобы pytest не выдавал предупреждения о неизвестных маркерах, их нужно зарегистрировать в файле конфигурации pytest.ini:

    Проверка исключений

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

    Для этого используется контекстный менеджер pytest.raises:

    Практика

    Задача 1: Рефакторинг с фикстурами

    У нас есть класс UserManager. Напишите для него тесты, используя фикстуру для создания менеджера перед каждым тестом.

    Решение:

    Задача 2: Параметризация валидации пароля

    Функция is_valid_password(password) возвращает True, если пароль длиннее 8 символов. Проверьте граничные значения.

    Решение:

    Домашнее задание

    Контекст: Вы продолжаете работать над интернет-магазином. У вас есть класс Product и класс Cart.

    Задание:

  • Создайте файл test_shop.py.
  • Напишите фикстуру product_iphone, которая возвращает объект Product("iPhone", 1000, 10).
  • Напишите тест test_reduce_stock_success, который использует фикстуру, уменьшает сток на 5 и проверяет, что осталось 5.
  • Напишите параметризованный тест test_reduce_stock_failure, который пытается списать больше товара, чем есть (например, 11, 100), и проверяет, что вызывается исключение ValueError.
  • Добавьте маркер @pytest.mark.critical к тесту успешного списания.
  • Критерии приемки: * Используется yield не обязательно, но return в фикстуре обязателен. * Параметризация покрывает минимум 2 кейса превышения стока. * Используется pytest.raises. * Тесты запускаются командой pytest -v.

    Типичные ошибки

  • Забыли test_: Функция называется check_login, и pytest её игнорирует.
  • Мутабельные фикстуры: Если вы используете scope="module" для объекта, который меняется в тестах (например, список), изменения из первого теста повлияют на второй. Для изменяемых объектов используйте scope="function".
  • Сложная логика в тестах: Если в тесте есть if, for или try-except (кроме pytest.raises), вы, скорее всего, делаете что-то не так. Тест должен быть линейным.
  • В следующем модуле мы перейдем к тестированию API и научимся отправлять настоящие HTTP-запросы.

    3. API-тестирование: requests, REST, mocking и JSON Schema

    API-тестирование: requests, REST, mocking и JSON Schema

    Добро пожаловать на третью неделю курса «Автоматизированное тестирование на Python». Мы уже заложили фундамент, разобравшись с пирамидой тестирования, и освоили наш главный инструмент — pytest. Теперь пришло время подняться на ступень выше: от модульных тестов (Unit) к интеграционным, а именно — к тестированию API.

    API (Application Programming Interface) — это «клей», который соединяет фронтенд, бэкенд, базы данных и сторонние сервисы. Тестирование API находится в середине пирамиды: эти тесты медленнее юнитов, но гораздо быстрее и стабильнее, чем UI-тесты через браузер. Именно здесь автоматизатор проводит большую часть своего времени.

    В этом модуле мы научимся отправлять HTTP-запросы на Python, проверять ответы сервера, валидировать сложные структуры данных и изолировать тесты от внешнего мира.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Понимать принципы REST архитектуры и структуру HTTP-запросов. * Использовать библиотеку requests для взаимодействия с API. * Писать автотесты для CRUD-операций (Create, Read, Update, Delete). * Валидировать структуру ответов с помощью JSON Schema. * Применять Mocking для изоляции тестов от реальных сервисов.

    Теория: REST и HTTP

    Большинство современных веб-сервисов общаются по протоколу HTTP, следуя архитектурному стилю REST (Representational State Transfer). Чтобы тестировать API, нужно понимать, из чего состоит диалог между клиентом (вашим тестом) и сервером.

    !Визуализация цикла запрос-ответ в протоколе HTTP

    Анатомия HTTP-запроса

  • URL (Endpoint): Адрес ресурса, к которому мы обращаемся (например, https://api.shop.com/users/123).
  • Method (Метод): Глагол, указывающий действие.
  • * GET — получить данные (безопасный метод, не меняет состояние сервера). * POST — создать новый ресурс. * PUT — полностью обновить существующий ресурс. * PATCH — частично обновить ресурс. * DELETE — удалить ресурс.
  • Headers (Заголовки): Метаданные (например, Authorization для токена или Content-Type: application/json).
  • Body (Тело): Данные, которые мы отправляем (обычно в формате JSON). У GET запросов тела, как правило, нет.
  • Анатомия HTTP-ответа

  • Status Code (Код состояния): Число, сообщающее результат.
  • * 2xx (Success): Всё хорошо. 200 OK, 201 Created. * 4xx (Client Error): Ошибка в запросе. 400 Bad Request, 401 Unauthorized, 404 Not Found. * 5xx (Server Error): Сервер упал. 500 Internal Server Error, 503 Service Unavailable.
  • Body (Тело): Данные, которые вернул сервер (JSON, XML, HTML).
  • Практика: Библиотека requests

    Python имеет встроенную библиотеку urllib, но в реальной работе стандартом де-факто является библиотека requests. Она проста, элегантна и делает HTTP-запросы понятными для человека.

    Установка:

    Первый запрос

    Давайте попробуем получить данные с публичного тестового API.

    Объединяем с pytest

    Теперь превратим это в настоящий тест. Создайте файл test_api_simple.py.

    Обратите внимание: аргумент json= в методе post автоматически добавляет заголовок Content-Type: application/json и сериализует словарь в строку.

    Валидация данных: JSON Schema

    Проверять каждое поле вручную (assert data['id'] == 1, assert data['name'] == 'Bob') утомительно и ненадежно. Что если API вернет id как строку "1", а не число? Или забудет обязательное поле?

    Для проверки структуры ответа (контракта) используют JSON Schema. Это стандарт описания формата JSON данных.

    Установка библиотеки:

    Пример использования в тесте:

    В реальных проектах схемы часто выносят в отдельные файлы, чтобы переиспользовать их в разных тестах.

    Mocking: Изоляция тестов

    Интеграционные тесты хороши, но у них есть проблемы:

  • Нестабильность: Внешний API может упасть, и ваш тест покраснеет, хотя ваш код работает верно.
  • Скорость: Сетевые запросы занимают время.
  • Стоимость: Некоторые API платные за каждый вызов.
  • Сложные сценарии: Трудно заставить реальный сервер вернуть ошибку 500 по заказу.
  • Здесь помогает Mocking (имитация). Мы подменяем реальный вызов requests.get на нашу «куклу», которая мгновенно возвращает нужный нам ответ.

    Для pytest лучшим решением является плагин pytest-mock.

    Установка:

    Пример использования фикстуры mocker

    С помощью моков вы можете проверить, как ваше приложение ведет себя, если API вернет 404, 500 или некорректный JSON, не дожидаясь реальных сбоев сервера.

    Полезные ресурсы

    * Документация Requests * JSON Schema Validator (онлайн) * Документация pytest-mock

    ---

    Домашнее задание

    Контекст: Вы тестируете API книжного магазина. Базовый URL: https://restful-booker.herokuapp.com (это реальный тренировочный API).

    Задача 1: Создание токена (Auth) Напишите тест, который отправляет POST запрос на /auth с телом {"username": "admin", "password": "password123"}. Проверьте, что статус код 200 и в ответе есть поле token (строка).

    Задача 2: CRUD операции с книгами Используя фикстуры pytest, реализуйте сценарий:

  • Create: Создайте бронирование (Booking) методом POST на /booking. Сохраните bookingid.
  • Read: Получите данные этого бронирования по ID методом GET. Проверьте, что имя совпадает с созданным.
  • Delete: Удалите бронирование методом DELETE (потребуется токен из Задачи 1 в заголовке Cookie: token=...).
  • Check: Попробуйте снова получить удаленное бронирование и убедитесь, что статус код 404.
  • Задача 3: Валидация схемы Опишите JSON Schema для метода GET /booking/{id} и добавьте валидацию в тест из Задачи 2.

    Критерии приемки: * Используется pytest и requests. * URL вынесен в константу или фикстуру. * Тесты независимы (если возможно) или логически связаны в цепочку. * Присутствует валидация JSON Schema.

    Типичные ошибки новичков

  • Зависимость от порядка тестов: Если тест A создает пользователя, а тест B его удаляет, то запуск только теста B приведет к ошибке. Используйте фикстуры для подготовки данных.
  • Хардкод URL: Не пишите http://localhost:8080 в каждом тесте. Выносите это в конфиг или conftest.py.
  • Игнорирование статус-кодов: Проверять только тело ответа (assert data['id'] == 1) недостаточно. Если сервер вернет 200 OK, но с текстом ошибки внутри HTML, ваш парсер JSON упадет. Всегда сначала проверяйте status_code.
  • Отсутствие таймаутов: В реальной жизни запросы могут зависать. Хорошей практикой считается указывать requests.get(url, timeout=5).
  • 4. UI-тестирование: Playwright, Page Object Model и ожидания

    UI-тестирование: Playwright, Page Object Model и ожидания

    Мы добрались до вершины пирамиды тестирования. Если Unit-тесты проверяют отдельные «кирпичики» кода, а API-тесты — коммуникацию между сервисами, то UI-тесты (User Interface) проверяют продукт так, как его видит конечный пользователь. Они открывают настоящий браузер, кликают по кнопкам, заполняют формы и проверяют, что верстка не «поехала», а бизнес-сценарий выполняется корректно.

    Долгое время стандартом индустрии был Selenium. Однако он имеет архитектурные ограничения, приводящие к медленной работе и нестабильным тестам. В этом модуле мы изучим Playwright — современный инструмент от Microsoft, который стал де-факто стандартом для новых проектов на Python.

    Цель модуля

    К концу этой недели вы будете уметь:

  • Настраивать окружение для UI-тестирования с Playwright и pytest.
  • Понимать разницу между явными, неявными и автоматическими ожиданиями.
  • Находить элементы на странице, используя надежные (user-facing) локаторы.
  • Применять паттерн Page Object Model (POM) для создания масштабируемой архитектуры тестов.
  • Теория: Playwright против Selenium и основы архитектуры

    Почему Playwright?

    Selenium WebDriver работает по протоколу HTTP. Он отправляет команду драйверу (например, chromedriver), тот переводит её браузеру, браузер выполняет действие и возвращает ответ. Эта цепочка создает задержки и является причиной «мигающих» (flaky) тестов.

    Playwright работает иначе. Он использует протокол отладчика (Chrome DevTools Protocol) и общается с браузером напрямую через WebSocket. Это обеспечивает высокую скорость выполнения и полный контроль над сетевыми запросами и событиями браузера.

    !Сравнение архитектуры: Selenium использует посредника (WebDriver), Playwright общается с браузером напрямую.

    Ключевые преимущества Playwright:

    * Auto-waiting (Авто-ожидание): Playwright автоматически ждет, пока элемент станет видимым, активным и кликабельным, прежде чем выполнить действие. Вам больше не нужно писать time.sleep(). * Изоляция: Каждый тест запускается в новом «контексте» (Browser Context). Это аналог вкладки «инкогнито»: куки и кэш не пересекаются между тестами. Это быстрее, чем перезапускать браузер целиком. * Trace Viewer: Встроенный инструмент отладки, который записывает видео, скриншоты и сетевые запросы для каждого шага теста.

    Локаторы: Как найти элемент?

    Локатор — это правило поиска элемента на странице. Стабильность ваших тестов на 90% зависит от качества локаторов.

    Плохие локаторы (хрупкие): * xpath=/html/body/div[2]/div/span/button — сломается при малейшем изменении верстки. * css=.btn-primary.red — сломается, если дизайнер поменяет цвет кнопки.

    Хорошие локаторы (User-Facing): Playwright рекомендует искать элементы так, как их ищет пользователь (по тексту или роли).

    * page.get_by_role("button", name="Войти") — самый надежный вариант. * page.get_by_label("Имя пользователя") — отлично для полей ввода. * page.get_by_text("Добро пожаловать") — для проверки контента. * page.get_by_test_id("submit-order") — если разработчики добавили специальные атрибуты data-testid.

    Паттерн Page Object Model (POM)

    Если писать все селекторы прямо в тестах, то при изменении верстки вам придется править сотни файлов. POM решает эту проблему, разделяя логику теста и описание страницы.

    * Page Object — это класс, описывающий страницу (локаторы и методы взаимодействия). * Test — это сценарий, который использует методы Page Object.

    Практика

    Для выполнения заданий вам потребуется установленный Python и pytest.

    Задача 1: Установка и первый тест

    Установим библиотеки и браузеры:

    Создайте файл test_login.py. Напишем простой тест для демо-сайта SauceDemo.

    Запустите тест командой:

    Флаг --headed позволяет увидеть браузер. Без него тест пройдет в фоновом режиме (headless).

    Задача 2: Работа со списками и фильтрацией

    Частая задача — выбрать элемент из списка. Дополним тест сценарием добавления товара в корзину.

    Задача 3: Рефакторинг с использованием Page Object Model

    Теперь превратим наш код в профессиональное решение. Создадим структуру:

    1. Base Page (pages/base_page.py) Базовый класс с общими методами.

    2. Login Page (pages/login_page.py) Класс для страницы логина. Локаторы и действия инкапсулированы здесь.

    3. Тест (tests/test_login_pom.py) Тест теперь чистый и читаемый.

    Типичные ошибки новичков

  • Использование time.sleep(): Никогда не используйте жесткие ожидания. Это замедляет тесты. Если Playwright не ждет элемент, используйте expect(locator).to_be_visible() или веб-ассерты.
  • Зависимые тесты: Каждый тест должен быть независимым. Нельзя, чтобы test_2 зависел от того, что test_1 что-то положил в корзину. Используйте фикстуры для подготовки данных.
  • Смешивание логики и локаторов: Не пишите page.click("#login-btn") внутри тестовой функции, если вы используете POM. Выносите это в методы страницы.
  • Игнорирование await: Если вы пишете асинхронные тесты (async/await), забытый await приведет к тому, что действие не выполнится, а тест пройдет ложно-положительно (или упадет позже).
  • Полезные ресурсы

    * Официальная документация Playwright Python * Генератор локаторов (Codegen) — запустите playwright codegen demo.playwright.dev/todomvc, чтобы сгенерировать код действий. * Best Practices — официальные рекомендации по написанию тестов.

    5. Управление тестовыми данными: Faker и форматы JSON/YAML

    Управление тестовыми данными: Faker и форматы JSON/YAML

    Добро пожаловать на пятую неделю курса «Автоматизированное тестирование на Python». Мы уже научились писать тесты, взаимодействовать с API и даже начали работать с UI. Но есть одна проблема, с которой сталкивается каждый автоматизатор, как только количество тестов переваливает за десяток: данные.

    Представьте, что вы написали тест регистрации пользователя. Вы использовали email test@example.com. Тест прошел успешно. Вы запускаете его второй раз, и он падает с ошибкой: «Пользователь с таким email уже существует». Вы меняете email на test2@example.com. А что делать на сотый раз?

    В этой статье мы разберем, как перестать хардкодить данные, как генерировать реалистичную информацию на лету с помощью библиотеки Faker и как вынести конфигурацию и тестовые наборы в внешние файлы форматов JSON и YAML.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Генерировать уникальные и реалистичные данные (имена, адреса, карты) с помощью Faker. * Понимать разницу между статическими и динамическими данными. * Делать тесты воспроизводимыми с помощью seeding. * Читать и писать файлы в форматах JSON и YAML в Python. * Разделять логику теста и тестовые данные (Data-Driven Testing).

    Теория

    1. Проблема тестовых данных

    В автоматизации данные делятся на два типа:

  • Статические данные: Константы, которые редко меняются (URL стенда, логин администратора, список категорий товаров).
  • Динамические данные: Уникальные значения для каждого запуска (email нового пользователя, номер заказа, текст комментария).
  • Хардкод (жесткое прописывание данных в коде) — это зло. Он делает тесты хрупкими и зависимыми друг от друга. Хороший тест должен быть изолированным: он сам создает свои данные и сам их удаляет (или они уникальны и не мешают другим).

    !Диаграмма, показывающая отделение кода тестов от источников данных

    2. Библиотека Faker

    Faker — это мощная библиотека для генерации поддельных данных. Она позволяет создавать имена, адреса, тексты, банковские карты, даты и многое другое. Главное преимущество — данные выглядят как настоящие, что позволяет проходить валидацию на бэкенде и фронтенде.

    Установка:

    #### Базовое использование

    #### Локализация

    Если вы тестируете приложение для российского рынка, вам нужны кириллические имена и адреса. Faker поддерживает это из коробки.

    #### Детерминизм (Seeding)

    Одна из проблем случайных данных — flaky-тесты (мигающие тесты). Представьте, что Faker сгенерировал имя с апострофом O'Connor, и ваш сайт упал из-за SQL-инъекции. Вы перезапускаете тест, Faker генерирует Smith, и тест проходит. Вы не можете воспроизвести баг.

    Чтобы этого избежать, используется seed (зерно). Если установить seed, последовательность генерации будет всегда одинаковой.

    3. Форматы хранения данных: JSON и YAML

    Хранить большие объемы статических данных (например, тело сложного запроса или конфиг окружения) прямо в .py файле неудобно. Для этого используют форматы сериализации.

    #### JSON (JavaScript Object Notation)

    Стандарт де-факто для REST API. Python имеет встроенную поддержку.

    * Плюсы: Родной формат для веб-сервисов, строгий синтаксис. * Минусы: Нет комментариев, строгие правила (запятые, кавычки), плохая читаемость больших файлов.

    Пример data.json:

    #### YAML (YAML Ain't Markup Language)

    Стандарт для конфигурационных файлов (Docker, Kubernetes, CI/CD, настройки фреймворков).

    * Плюсы: Очень чистый синтаксис, поддержка комментариев, меньше скобок. * Минусы: Зависимость от отступов (как в Python), требует установки библиотеки PyYAML.

    Пример config.yaml:

    Установка библиотеки для YAML:

    ---

    Практика

    Перейдем к коду. Мы создадим модуль генерации данных и научимся загружать конфиги.

    Задача 1: Генератор пользователей с Faker

    Создадим класс, который генерирует данные для регистрации. Это полезно для паттерна Object Mother или Data Builder.

    Задача 2: Работа с JSON

    Допустим, у нас есть файл payloads.json, где хранятся шаблоны ответов от API, которые мы используем для моков (mocking).

    Задача 3: Конфигурация через YAML

    Создадим файл config.yaml и напишем фикстуру pytest, которая делает эти настройки доступными во всех тестах.

    Файл config.yaml:

    Файл conftest.py:

    Задача 4: Комбинируем подходы

    Частая задача: взять шаблон JSON и заменить в нем пару полей на динамические данные от Faker.

    ---

    Домашнее задание

    Контекст: Вы тестируете интернет-магазин. Вам нужно проверять создание заказов.

    Задание:

  • Создайте файл test_data.yaml, содержащий список товаров (статические данные): название, цена, категория.
  • Напишите класс OrderBuilder, который использует Faker для генерации адреса доставки и имени клиента.
  • Реализуйте функцию create_order_json, которая:
  • * Читает товары из YAML. * Выбирает случайный товар. * Генерирует данные клиента через Faker. * Сохраняет итоговый заказ в файл order_<timestamp>.json.
  • Напишите тест (можно без реальной отправки запроса), который вызывает эту функцию и проверяет, что в созданном JSON файле есть поле total_price (цена товара + стоимость доставки, которую можно придумать).
  • Критерии приемки: * Используется yaml.safe_load. * Используется Faker с локалью ru_RU. * Код разбит на функции/классы. * Файлы создаются и читаются без ошибок кодировки.

    Типичные ошибки новичков

  • Случайность без логирования: Тест упал на специфических данных от Faker, но вы не вывели их в консоль/отчет. Вы не знаете, на каких именно данных упал тест. Решение: Всегда логируйте сгенерированные данные перед шагом Act.
  • JSON с комментариями: Попытка добавить комментарий // или # в .json файл приведет к ошибке парсинга. JSON не поддерживает комментарии.
  • Забытый __init__.py: При попытке импортировать данные из соседней папки Python не видит модуль. Убедитесь, что пакеты инициализированы.
  • Сложная логика генерации: Если ваш генератор данных занимает 100 строк и имеет кучу условий if/else, вам придется писать тесты на ваши тесты. Держите генераторы простыми.
  • Полезные ресурсы

    * Документация Faker * Официальный сайт JSON * Руководство по YAML * PyYAML documentation

    В следующем модуле мы займемся инфраструктурой: настроим pytest.ini, научимся логировать действия и генерировать красивые отчеты Allure.

    6. Инфраструктура: конфигурация, логирование и отчеты Allure

    Инфраструктура: конфигурация, логирование и отчеты Allure

    Добро пожаловать на шестую неделю курса. Мы уже умеем писать функциональные тесты для API и UI, генерировать данные и использовать моки. Но представьте ситуацию: у вас 500 тестов, они запускаются ночью в CI/CD, и утром вы видите, что 50 из них упали. В консоли — мешанина из ошибок, принтов и трейсбеков. Менеджер спрашивает: «Насколько стабилен релиз?», а вы не можете ответить, пока не разберете логи вручную.

    Отличие Junior от Senior специалиста часто кроется не в умении написать тест, а в умении построить инфраструктуру, которая делает результаты тестирования прозрачными, а отладку — быстрой. Сегодня мы превратим ваш тестовый проект в профессиональный инструмент с помощью глобальной конфигурации, правильного логирования и интерактивных отчетов Allure.

    Цель модуля

    К концу этого занятия вы настроите проект так, чтобы:

    * Запуск тестов выполнялся одной короткой командой pytest. * Вместо print() использовалось структурированное логирование, сохраняемое в файл. * Генерировался красивый HTML-отчет с историей запусков. * К упавшим UI-тестам автоматически прикреплялись скриншоты, а к API-тестам — дампы запросов.

    ---

    1. Конфигурация: pytest.ini

    Вы наверняка устали каждый раз писать в терминале длинные команды вроде:

    Файл pytest.ini — это центр управления полетами вашего проекта. Он позволяет зафиксировать настройки запуска, зарегистрировать маркеры и определить переменные окружения.

    !Файл конфигурации управляет тем, как pytest находит, запускает и обрабатывает тесты.

    Практика: Создание конфигурации

    Создайте файл pytest.ini в корне вашего проекта. Это позволит стандартизировать запуск тестов для всей команды.

    Теперь для запуска всех тестов с нужными параметрами достаточно набрать просто pytest.

    > Важно: Никогда не храните в pytest.ini секреты (пароли, токены). Этот файл попадает в репозиторий. Для секретов используйте библиотеку python-dotenv и файлы .env, добавленные в .gitignore.

    Полезные ссылки: * Документация по конфигурации pytest

    ---

    2. Логирование: почему print — это плохо

    Новички используют print(), чтобы понять, что происходит в тесте. Профессионалы используют модуль logging. Почему?

  • Уровни важности: Вы можете отфильтровать вывод. DEBUG — для разработки, INFO — для отчета, ERROR — для алертов.
  • Контекст: Логи автоматически добавляют время, имя файла и номер строки.
  • Потоки: print может смешиваться в кашу при параллельном запуске тестов (pytest-xdist), а logging потокобезопасен.
  • Гибкость: Логи можно одновременно писать в консоль, в файл и отправлять в систему мониторинга (Sentry/ELK).
  • Практика: Настройка логирования в pytest.ini

    Добавьте секцию логирования в ваш pytest.ini. Это активирует встроенный плагин логирования pytest.

    Использование в коде

    В тестах и фикстурах мы создаем экземпляр логгера.

    ---

    3. Отчеты Allure: Визуализация качества

    Стандартный вывод pytest (.F..) понятен инженеру, но бесполезен для бизнеса. Allure Framework — это стандарт индустрии для построения красивых, интерактивных отчетов.

    Установка

    Вам понадобятся две вещи:

  • Библиотека Python: pip install allure-pytest
  • Утилита командной строки Allure (требует Java).
  • * macOS: brew install allure * Windows: scoop install allure или скачать архив с официального GitHub релиза.

    Разметка тестов (Allure Decorators)

    Чтобы отчет был читаемым, тесты нужно «размечать». Это помогает группировать их по функционалу.

    Генерация отчета

  • Запустите тесты: pytest (благодаря pytest.ini флаг --alluredir применится сам).
  • В папке allure-results появятся JSON-файлы. Это «сырые» данные.
  • Сгенерируйте и откройте отчет одной командой:
  • Браузер откроет дашборд с графиками, временем выполнения и списком тестов.

    ---

    4. Продвинутая практика: Hooks и conftest.py

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

    Для этого мы используем файл conftest.py и хуки pytest.

    Задача: Автоматический скриншот при падении

    Добавьте этот код в ваш conftest.py:

    Теперь, если UI-тест упадет, в отчете Allure во вкладке теста появится скриншот.

    ---

    Типичные ошибки новичков

  • Отсутствие pytest.ini: Каждый разработчик запускает тесты со своими флагами, что приводит к разным результатам.
  • print вместо логгера: На CI сервере логи теряются или смешиваются, делая отладку невозможной.
  • Хранение отчетов в Git: Папку allure-results и allure-report нужно добавить в .gitignore. В репозитории должен быть только код.
  • Слишком много шагов: Не нужно оборачивать в with allure.step каждую строчку кода. Выделяйте только логические блоки (например, "Авторизация", "Заполнение формы").
  • Абсолютные пути: Указание путей к логам или драйверам как C:\Users\Admin\project сломает тесты у коллеги. Используйте pathlib и относительные пути.
  • Итоги модуля

    Теперь ваша инфраструктура выглядит профессионально: * Конфигурация едина для всей команды. * Логи пишутся в файл и структурированы. * Отчеты информативны и содержат скриншоты ошибок.

    В следующем модуле мы перейдем к CI/CD и научимся запускать эти тесты автоматически в GitHub Actions.

    7. CI/CD: автоматизация запуска тестов в GitHub Actions

    CI/CD: автоматизация запуска тестов в GitHub Actions

    Мы прошли долгий путь: от написания первой функции на Python до создания сложной архитектуры с UI, API тестами и генерацией отчетов. Но пока что все эти тесты запускаются вручную на вашем локальном компьютере. В реальной разработке фраза «у меня локально всё работает» — это не аргумент.

    Представьте команду из 10 разработчиков. Каждый день они вносят изменения в код. Если они забудут запустить тесты перед слиянием кода (merge), проект сломается. Если у одного разработчика Python 3.10, а у другого 3.12 — поведение может отличаться.

    Здесь на помощь приходит CI/CD. В этой статье мы автоматизируем запуск ваших тестов с помощью GitHub Actions, чтобы они проверяли код при каждом сохранении.

    Что такое CI/CD?

    Это акроним, объединяющий две практики:

  • CI (Continuous Integration — Непрерывная интеграция): Практика частой сборки проекта и запуска автотестов. Главная цель — как можно раньше найти баги. Если тесты упали, изменения отклоняются.
  • CD (Continuous Delivery / Deployment — Непрерывная доставка): Автоматическая выкладка проверенного кода на боевые серверы (Production).
  • Для QA-инженера (особенно автоматизатора) критически важна часть CI. Наша задача — настроить «робота», который будет беспристрастно проверять код 24/7.

    !Визуализация процесса непрерывной интеграции: от коммита до отчета о тестировании

    Почему GitHub Actions?

    Существует множество инструментов: Jenkins, GitLab CI, TeamCity, CircleCI. Однако GitHub Actions (GHA) стал стандартом де-факто для проектов, хостящихся на GitHub, по нескольким причинам:

    * Интеграция: Не нужно настраивать отдельные серверы, всё работает внутри GitHub. * Бесплатно: Для публичных репозиториев (и щедрые лимиты для приватных). * Конфигурация как код: Весь процесс описывается в YAML-файле, который лежит в вашем репозитории.

    Анатомия GitHub Actions

    Чтобы «магия» заработала, нужно создать файл конфигурации. GitHub ищет их в специальной папке: .github/workflows/.

    Основные понятия:

  • Workflow (Рабочий процесс): Весь файл конфигурации. Запускается по событию (например, push).
  • Job (Задача): Набор шагов, выполняемых на одной виртуальной машине.
  • Runner (Раннер): Виртуальный сервер (обычно Ubuntu), который GitHub выделяет для выполнения вашей задачи.
  • Step (Шаг): Конкретное действие (команда в терминале или готовый экшен).
  • Практика: Создаем первый пайплайн

    Давайте настроим автоматический запуск тестов pytest при каждом пуше в ветку main или при создании Pull Request.

    Шаг 1: Структура файла

    Создайте в корне проекта папку .github, внутри неё папку workflows, а в ней файл tests.yml.

    Шаг 2: Получение кода и настройка окружения

    Раннер запускается абсолютно пустым. На нем нет ни вашего кода, ни Python, ни зависимостей. Нам нужно всё это настроить.

    Мы будем использовать готовые Actions (экшены) от сообщества и самого GitHub. Это как библиотеки, только для CI.

    Дополним секцию steps:

    Обратите внимание на синтаксис run: |. Вертикальная черта позволяет писать многострочные команды bash.

    Шаг 3: Запуск тестов

    Теперь, когда окружение готово, мы можем запустить pytest. Вспомним про наш файл pytest.ini, который мы настроили в прошлом модуле — он автоматически подхватит настройки.

    Полный файл конфигурации

    Вот как выглядит итоговый файл .github/workflows/tests.yml:

    Теперь сделайте коммит и отправьте его на GitHub (git push). Перейдите во вкладку Actions в вашем репозитории. Вы увидите, как запустился ваш workflow. Если тесты пройдут успешно — он станет зеленым.

    Работа с артефактами: Сохраняем отчеты

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

    Чтобы сохранить файлы после завершения работы, используются Artifacts.

    Добавим шаг сохранения результатов Allure:

    Разбор: * if: always(): Критически важно. По умолчанию, если шаг с тестами падает (exit code != 0), следующие шаги не выполняются. Но нам нужен отчет именно тогда, когда тесты упали! * path: Путь к папке, которую нужно сохранить.

    Теперь после прогона в интерфейсе GitHub Actions внизу страницы появится zip-архив с результатами, который можно скачать.

    Продвинутые техники

    1. Матрица тестирования (Matrix Build)

    Что если вы пишете библиотеку и хотите убедиться, что она работает и на Python 3.9, и на 3.12? Не нужно копировать джобы. Используйте стратегию matrix.

    GitHub запустит 4 параллельных джобы, по одной для каждой версии.

    2. Кэширование зависимостей

    Установка requirements.txt может занимать минуты. Чтобы ускорить процесс, можно кэшировать скачанные пакеты.

    В setup-python это делается одной строчкой:

    Branch Protection Rules

    Настройка CI — это полдела. Нужно запретить вливать код, который не прошел тесты.

  • Зайдите в настройки репозитория (Settings).
  • Выберите Branches -> Add branch protection rule.
  • Укажите паттерн ветки (обычно main).
  • Поставьте галочку Require status checks to pass before merging.
  • Найдите в поиске название вашей джобы (например, build-and-test).
  • Теперь кнопка "Merge" будет заблокирована, пока GitHub Actions не загорится зеленым.

    Типичные ошибки новичков

  • Неправильные отступы в YAML: YAML крайне чувствителен к пробелам (как Python). Если отступ сбит, пайплайн не запустится. Используйте линтеры или IDE.
  • Отсутствие requirements.txt: Раннер не знает, какие библиотеки нужны вашему проекту. Не забывайте обновлять файл зависимостей (pip freeze > requirements.txt) и коммитить его.
  • Хардкод переменных: Если ваши тесты требуют пароль от БД, не пишите его в YAML. Используйте GitHub Secrets (Settings -> Secrets and variables -> Actions) и обращайтесь к ним как ${{ secrets.DB_PASSWORD }}.
  • Забытый if: always(): Тесты упали, вы хотите посмотреть почему, а отчет не загрузился, потому что шаг загрузки артефактов был пропущен из-за ошибки в предыдущем шаге.
  • Домашнее задание

    Контекст: У вас есть репозиторий с проектом, содержащим UI и API тесты.

    Задание:

  • Создайте файл .github/workflows/ci.yml.
  • Настройте запуск тестов на события push и pull_request.
  • Добавьте шаг установки зависимостей с кэшированием.
  • Настройте сохранение папки allure-results и файла pytest.log (если вы настроили логирование в файл) в качестве артефактов.
  • Сделайте Pull Request с заведомо падающим тестом и убедитесь, что GitHub пометил проверку как неудачную (красный крестик), но артефакты всё равно загрузились.
  • Критерии приемки: * Пайплайн успешно запускается. * Используется actions/checkout и actions/setup-python. * Артефакты доступны для скачивания в интерфейсе GitHub.

    Полезные ресурсы

    * Официальная документация GitHub Actions * Python in GitHub Actions * GitHub Actions Marketplace — поиск готовых экшенов.

    В следующем модуле мы упакуем наши тесты в Docker, чтобы они работали идентично на любой машине, независимо от установленной ОС.

    8. Контейнеризация: Docker и Docker Compose для тестов

    Контейнеризация: Docker и Docker Compose для тестов

    Добро пожаловать на восьмую неделю курса «Автоматизированное тестирование на Python». В прошлом модуле мы настроили CI/CD в GitHub Actions и увидели, как удобно запускать тесты в облаке. Но заметили ли вы одну деталь? GitHub Actions каждый раз выделяет нам «чистую» виртуальную машину, на которую мы заново устанавливаем Python и зависимости.

    А что, если тесты нужно запустить локально, но у коллеги стоит Python 3.8, у вас 3.11, а на сервере вообще Linux без графической оболочки? Или вашим тестам нужна конкретная версия базы данных PostgreSQL, которую долго и сложно устанавливать вручную?

    Здесь на помощь приходит контейнеризация. Сегодня мы научимся упаковывать наши автотесты в Docker, чтобы они работали идентично на любой машине — будь то ноутбук разработчика, сервер тестировщика или CI-пайплайн. Мы избавимся от проблемы «на моем компьютере всё работает» раз и навсегда.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Понимать разницу между виртуальной машиной и контейнером. * Писать Dockerfile для проекта с автотестами. * Использовать Docker Compose для поднятия тестовой инфраструктуры (тесты + тестируемое приложение + база данных). * Настраивать сетевое взаимодействие между контейнерами. * Получать отчеты (Allure) из изолированного контейнера на хост-машину.

    Теория: Что такое Docker?

    Представьте, что вы переезжаете в другой дом. Вы можете переносить вещи по одной: сначала книги, потом посуду, потом мебель. В процессе что-то потеряется, что-то разобьется, а диван не пролезет в дверь нового дома.

    Docker предлагает другой подход: вы упаковываете всё содержимое комнаты в стандартный железный морской контейнер. Грузчикам (операционной системе) всё равно, что внутри — пианино или коробки с книгами. Они просто грузят контейнер. Если он поместился на корабль в порту отправления, он гарантированно поместится на такой же корабль в порту назначения.

    В мире IT: * Приложение (или тесты) — это груз. * Docker Image (Образ) — это запечатанный контейнер с описью содержимого. * Docker Container — это запущенный процесс, работающий внутри этого изолированного пространства.

    !Сравнение архитектуры виртуальных машин и контейнеров Docker

    Основные понятия

  • Dockerfile: Текстовая инструкция (рецепт), как собрать образ. Например: «Возьми Python, скопируй туда мой код, установи библиотеки».
  • Image (Образ): Неизменяемый файл, результат сборки Dockerfile. Это «слепок» системы. Образы хранятся в реестрах (например, Docker Hub).
  • Container (Контейнер): Живой экземпляр образа. Вы можете запустить 10 контейнеров из одного образа одновременно.
  • Volume (Том): Способ «пробросить» папку с вашего компьютера внутрь контейнера или наоборот. Это нужно, чтобы данные (например, отчеты тестов) не исчезли после удаления контейнера.
  • Docker Compose

    Если Docker запускает один контейнер, то Docker Compose — это дирижер оркестра. Он позволяет описать в одном YAML-файле сразу несколько сервисов (например, web-app, database, tests) и запустить их одной командой. Compose автоматически настраивает сеть, чтобы контейнеры «видели» друг друга по именам.

    Полезные ресурсы: * Официальная документация Docker (Get Started) * Best practices for writing Dockerfiles

    ---

    Практика

    Для работы вам понадобится установленный Docker Desktop (для Windows/Mac) или Docker Engine (для Linux).

    Задача 1: Пишем Dockerfile для тестов

    Допустим, у нас есть стандартный проект с тестами:

    Создайте файл с именем Dockerfile (без расширения) в корне проекта:

    Важный момент: Чтобы не копировать лишнее (виртуальное окружение .venv, кэш __pycache__, .git), создайте файл .dockerignore рядом с Dockerfile:

    Теперь соберем образ. Откройте терминал в папке проекта:

    После успешной сборки запустим тесты:

    Флаг --rm означает «удалить контейнер сразу после завершения работы». Вы увидите вывод pytest прямо в консоли, как будто запускали его локально.

    Задача 2: Сохранение отчетов (Volumes)

    Если тесты упадут или сгенерируют отчет Allure, эти файлы останутся внутри контейнера. Как только контейнер остановится, файлы исчезнут. Нам нужно «вытащить» их наружу.

    Используем механизм Volumes (томов).

    Теперь, после прогона тестов, папка allure-results на вашем компьютере наполнится JSON-файлами из контейнера.

    Задача 3: Docker Compose для интеграционных тестов

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

  • Поднять базу данных (PostgreSQL).
  • Поднять само приложение (API).
  • Запустить тесты, которые будут стучаться в это API.
  • Вручную это делать долго. Опишем инфраструктуру в docker-compose.yml.

    Ключевой момент: Сеть (Networking)

    Внутри Docker Compose не работает localhost так, как вы привыкли. * Если тесты пишут requests.get("http://localhost:8000"), они стучатся в свой собственный контейнер, где никакого API нет. * Чтобы достучаться до соседнего контейнера, нужно использовать его имя сервиса как хостнейм. В нашем случае: http://sut:8000.

    Запуск всей системы:

    Задача 4: Проблема ожидания (Wait-for-it)

    Если вы запустите конфигурацию выше, тесты могут упасть сразу же. Почему? Потому что контейнер db и sut запустились, но приложению нужно время (секунды), чтобы инициализировать базу данных и начать принимать запросы. А тесты стартуют мгновенно.

    depends_on ждет только запуска контейнера, а не готовности приложения.

    Решение: использовать скрипты ожидания или логику retry внутри тестов.

    Простой способ в Python (в conftest.py):

    ---

    Домашнее задание

    Контекст: Используйте проект с API-тестами из 3-й недели (Bookstore API или любой публичный API).

    Задание:

  • Напишите Dockerfile для вашего проекта. Убедитесь, что используете .dockerignore.
  • Создайте docker-compose.yml, в котором будет только один сервис — tests.
  • Настройте Volume так, чтобы после прогона команды docker-compose up у вас на локальном компьютере в папке report/ появлялся сгенерированный HTML отчет Allure (вам понадобится команда allure generate внутри контейнера или просто сохранение allure-results для генерации локально).
  • Усложнение: Добавьте в Compose сервис selenium/standalone-chrome и настройте ваши UI-тесты (если есть) на использование Remote WebDriver, указывающего на этот контейнер.
  • Критерии приемки: * Команда docker-compose up успешно прогоняет тесты и завершается. * В консоли видны логи тестов. * На хост-машине появляются файлы результатов. * В коде тестов нет хардкода локальных путей.

    Типичные ошибки новичков

  • Использование localhost внутри контейнера: Самая частая ошибка. Запомните: внутри контейнера localhost — это сам контейнер. Чтобы обратиться к хост-машине (вашему ноутбуку), на Windows/Mac используйте адрес host.docker.internal. Чтобы обратиться к другому сервису в Compose — используйте имя сервиса.
  • Копирование виртуального окружения: Если не добавить .venv в .dockerignore, Docker скопирует вашу локальную папку с библиотеками (которая может быть создана на Windows) в Linux-контейнер. Это приведет к странным ошибкам. Всегда устанавливайте зависимости заново внутри Dockerfile.
  • Запуск от root: По умолчанию Docker запускает процессы от root. В реальных проектах (особенно в банках) это запрещено безопасностью. Хорошей практикой считается создание пользователя: RUN useradd -m myuser && USER myuser.
  • Тяжелые образы: Использование FROM python:3.10 (полная версия) вместо slim или alpine может увеличить размер образа с 100 МБ до 1 ГБ. Для тестов лучше использовать python:3.10-slim.
  • В следующем модуле мы поговорим о продвинутых практиках: как ускорить тесты с помощью параллельного запуска и как писать сложные плагины для pytest.

    9. Продвинутый pytest: параллельный запуск и кастомные плагины

    Продвинутый pytest: параллельный запуск и кастомные плагины

    Добро пожаловать на девятую неделю курса «Автоматизированное тестирование на Python». Мы уже прошли огромный путь: от простых функций до упаковки тестов в Docker-контейнеры. Ваша инфраструктура работает, тесты запускаются в CI/CD, и отчеты Allure радуют глаз.

    Но по мере роста проекта возникает новая проблема: время. Когда тестов становится 500, 1000 или 5000, их последовательное выполнение может занимать часы. Ждать час, чтобы узнать, что ты пропустил запятую в коде — непозволительная роскошь. Кроме того, по мере усложнения логики вам может потребоваться функционал, которого нет в стандартном pytest.

    Сегодня мы переходим на уровень Senior-автоматизации. Мы научимся ускорять тесты в разы с помощью параллельного запуска и расширять возможности фреймворка, создавая собственные плагины.

    Цель модуля

    К концу этой недели вы будете уметь:

    * Ускорять выполнение тестов с помощью библиотеки pytest-xdist. * Решать проблемы состояния гонки (race conditions) при параллельном запуске. * Понимать архитектуру хуков (hooks) в pytest. * Писать собственные локальные и устанавливаемые плагины. * Управлять порядком выполнения и фильтрацией тестов через код.

    Часть 1: Параллельный запуск с pytest-xdist

    По умолчанию pytest запускает тесты последовательно: один за другим, в одном процессе. Это надежно, но медленно. Современные процессоры имеют много ядер, и большую часть времени при последовательном запуске эти ядра простаивают.

    Плагин pytest-xdist позволяет запускать тесты параллельно, используя несколько процессов (workers). Каждый воркер — это, по сути, отдельный мини-pytest, который берет порцию тестов из общей очереди и выполняет их.

    !Архитектура распределения тестов между процессами-воркерами

    Установка и запуск

    Установим плагин:

    Теперь у нас появился флаг -n (numprocesses).

    Запуск в 4 потока:

    Автоматическое определение количества ядер (рекомендуется):

    Главная проблема: Изоляция данных

    Параллелизация — это лакмусовая бумажка качества ваших тестов. Если при запуске pytest -n auto ваши тесты начинают падать хаотично (то падают, то проходят), значит, они не изолированы.

    Типичный сценарий провала:

  • Тест А (Воркер 1): Создает пользователя admin в базе данных.
  • Тест Б (Воркер 2): Проверяет, что список пользователей пуст, и очищает базу.
  • Тест А: Пытается залогиниться под admin, но Тест Б его уже удалил.
  • Результат: Ошибка UserNotFound.
  • Правило: Тесты, запускаемые параллельно, не должны зависеть от общего изменяемого состояния (одна и та же запись в БД, один и тот же файл, глобальные переменные).

    Решение проблем с фикстурами (Scope Session)

    В pytest-xdist есть нюанс: фикстуры с областью видимости scope="session" создаются для каждого воркера отдельно. Если ваша фикстура поднимает тяжелую базу данных, и у вас 4 воркера, база поднимется 4 раза. Это может убить производительность.

    Чтобы выполнить настройку один раз для всех процессов, используется файловая блокировка (File Lock). Pytest предоставляет фикстуру tmp_path_factory, которая помогает синхронизировать процессы.

    Пример правильной session фикстуры для xdist:

    Часть 2: Анатомия плагинов pytest

    Pytest построен на системе плагинов. Даже стандартные функции (например, assert или сбор тестов) реализованы как внутренние плагины. Это значит, что вы можете переопределить или расширить практически любое поведение фреймворка.

    Что такое Hook (Хук)?

    Хук — это точка в коде pytest, куда вы можете «вклиниться» со своей функцией. Все хуки начинаются с префикса pytest_.

    Основные этапы жизни pytest и их хуки:

  • Bootstrap (Загрузка): pytest_load_initial_conftests, pytest_cmdline_main.
  • Configuration (Настройка): pytest_addoption, pytest_configure.
  • Collection (Сбор тестов): pytest_collection_modifyitems.
  • Run (Запуск): pytest_runtest_setup, pytest_runtest_call, pytest_runtest_teardown.
  • Reporting (Отчеты): pytest_runtest_logreport, pytest_sessionfinish.
  • Где писать плагины?

  • conftest.py: Для локальных плагинов, специфичных для конкретного проекта.
  • Отдельный пакет: Для переиспользования кода между разными проектами (публикуется на PyPI или устанавливается через git).
  • Практика: Пишем свои плагины

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

    Сценарий 1: Добавление CLI-параметра (pytest_addoption)

    Задача: Мы хотим запускать тесты на разных окружениях (dev, stage, prod) передавая флаг --env.

    В файле conftest.py:

    Запуск:

    Сценарий 2: Пропуск медленных тестов (pytest_collection_modifyitems)

    Задача: У нас есть тесты, помеченные маркером @pytest.mark.slow. Мы хотим запускать их только если передан специальный флаг --run-slow, иначе пропускать.

    В файле conftest.py:

    Теперь pytest пропустит медленные тесты, а pytest --run-slow запустит их.

    Сценарий 3: Автоматическое добавление user-agent в отчет

    Задача: Добавить в отчет (например, в заголовок консоли) информацию о версии тестируемого приложения.

    Упаковка плагина

    Если вы хотите использовать свой плагин в 10 разных микросервисах, копировать conftest.py — плохая идея. Лучше создать устанавливаемый пакет.

    Структура папок:

    Содержимое pytest_myplugin.py — это просто код ваших хуков.

    Содержимое pyproject.toml:

    Ключевая магия здесь — entry-points. Группа pytest11 говорит pytest'у: «Эй, посмотри в модуль pytest_myplugin, там есть хуки».

    Установка:

    Полезные ресурсы

    * Документация pytest-xdist * Writing Plugins (официальная документация) * Список популярных плагинов

    ---

    Домашнее задание

    Контекст: Вы работаете над проектом из предыдущих модулей (API + UI тесты). Тесты стали занимать слишком много времени.

    Задача 1: Параллелизация

  • Установите pytest-xdist.
  • Запустите тесты с флагом -n auto.
  • Если какие-то тесты упали (из-за базы данных или файлов), исправьте их, используя фикстуры с уникальными данными (Faker) или временные директории (tmp_path), чтобы тесты не мешали друг другу.
  • Замерьте время выполнения с параллелизацией и без. Добавьте результаты в README.md.
  • Задача 2: Кастомный CLI-флаг

  • В conftest.py реализуйте хук pytest_addoption, добавляющий флаг --browser-mobile.
  • Напишите фикстуру, которая проверяет этот флаг. Если он True, то в настройках Playwright/Selenium устанавливается viewport (размер окна) как у iPhone 12 (390x844).
  • Напишите UI-тест, который проверяет наличие «гамбургер-меню» (оно появляется только на мобильных).
  • Задача 3: Динамический пропуск

  • Реализуйте логику в pytest_collection_modifyitems: если тесты запускаются на ОС Windows (проверка через sys.platform), то пропускать все тесты с маркером @pytest.mark.linux_only.
  • Критерии приемки: * Тесты проходят стабильно в 2+ потока. * Флаг --browser-mobile реально меняет размер окна браузера. * Код плагинов находится в conftest.py.

    Типичные ошибки

  • Глобальные переменные: Использование списка users = [] на уровне модуля для хранения созданных юзеров. В xdist каждый процесс получит свою копию списка, и вы не сможете их синхронизировать.
  • Отсутствие атомарности: Тест А создает файл config.json, Тест Б его читает. В параллельном режиме Тест Б может запуститься раньше, чем Тест А закончит запись.
  • Принты в хуках: Использование print() внутри хуков настройки может не сработать или вывестись в неожиданном месте, так как pytest перехватывает stdout. Используйте logging.
  • В следующем, заключительном модуле курса, мы разберем сложные сценарии: тестирование WebSocket, OAuth авторизацию и работу с базами данных напрямую.