Начните с риска и oracle, затем выберите самый дешёвый уровень теста, который даст полезное доказательство. Название инструмента само по себе не является стратегией.
Вопросы и ответы
12 подробных ответов
01Что такое Page Object Model и какую проблему он решает?
middle
Короткий ответ: POM — паттерн, где каждая страница или экран приложения описывается отдельным классом, инкапсулирующим локаторы и действия над ними. Тесты вызывают методы этих классов, а не сырые селекторы, поэтому при изменении вёрстки локатор правится в одном месте, а не в сотне тестов.
Подробно:
- Инкапсуляция локаторов — селекторы живут внутри page object, тест их не видит.
- Читаемость — тест выражен в терминах бизнеса (
loginPage.login(user, pass)), а не в CSS/XPath. - PageFactory — в Java-стеке
@FindBy+ ленивая инициализация элементов черезPageFactory.initElements. - Слой Steps поверх POM — бизнес-действия, комбинирующие несколько страниц; тест превращается в сценарий.
┌──────────────┐
│ Тест │ сценарий: что проверяем
└──────┬───────┘
▼
┌──────────────┐
│ Steps │ бизнес-действия (login, checkout)
└──────┬───────┘
▼
┌──────────────┐
│ Page Objects │ локаторы + действия страницы
└──────┬───────┘
▼
┌──────────────┐
│ WebDriver │ браузер
└──────────────┘
⚠️ Частая ошибка: объяснять POM как «так принято/модно» без «зачем». Ценность — единая точка правки при изменении UI и отделение локаторов от тест-логики.
02Что такое Data-Driven Testing и как его реализовать?
middle
Короткий ответ: DDT — подход, при котором одна и та же логика теста прогоняется на разных наборах входных данных, вынесенных наружу. Тест-данные отделены от тест-логики: добавить кейс — значит добавить строку данных, а не копировать тест.
Подробно:
- pytest —
@pytest.mark.parametrizeсо списком наборов. - TestNG —
@DataProvider, возвращающийObject[][]. - Внешние источники — CSV/JSON/Excel или БД: данные редактируют без изменения кода.
- Зачем — покрытие граничных значений и классов эквивалентности без дублирования тестов.
import pytest
@pytest.mark.parametrize("a, b, expected", [
(2, 3, 5),
(0, 0, 0),
(-1, 1, 0),
])
def test_add(a, b, expected):
assert add(a, b) == expected
⚠️ Частая ошибка: считать DDT «просто циклом внутри теста». В цикле первое падение прячет остальные кейсы, а отчёт показывает один тест; параметризация даёт отдельный кейс на каждый набор данных.
03Что такое фикстуры в pytest и какие у них scope?
middle
Короткий ответ: Фикстура — переиспользуемый setup/teardown, который pytest подставляет в тест через dependency injection (по имени аргумента). scope управляет тем, как часто фикстура пересоздаётся: function, class, module, package, session.
Подробно:
- DI по имени — тест объявляет
def test_x(db):, и pytest сам создаётdb. - Teardown через yield — код после
yieldвыполняется при разрушении фикстуры. - Scope — от самого узкого (function) до самого широкого (session); чем шире, тем дольше живёт объект.
import pytest
@pytest.fixture(scope="session")
def db():
conn = connect() # setup: один раз на прогон
yield conn
conn.close() # teardown
| scope | Пересоздаётся |
|---|---|
| function | на каждый тест (по умолчанию) |
| class | раз на класс |
| module | раз на файл |
| package | раз на пакет |
| session | раз на весь прогон |
⚠️ Частая ошибка: session/module-фикстура, отдающая мутабельный объект (список, dict, залогиненный клиент). Один тест меняет состояние — оно протекает в следующий, и тесты начинают зависеть от порядка запуска.
04Чем @pytest.mark.parametrize отличается от параметризации фикстуры через params?
middle
Короткий ответ: @pytest.mark.parametrize размножает один конкретный тест по наборам данных. Фикстура с params=[...] размножает каждый тест, который эту фикстуру использует. Разница — в области действия (area of effect): маркер локален для теста, параметризованная фикстура влияет на всех потребителей.
Подробно:
- parametrize — данные привязаны к одной тест-функции, ничего вокруг не трогают.
- fixture params — фикстура сама становится многовариантной; любой тест, зависящий от неё, прогонится на каждом варианте.
- Когда что — parametrize для входов конкретного теста; параметризованная фикстура для окружения (например, прогнать весь набор на нескольких браузерах).
# parametrize: множит ТОЛЬКО test_add
@pytest.mark.parametrize("n", [1, 2, 3])
def test_add(n):
...
# fixture params: множит КАЖДЫЙ тест, берущий browser
@pytest.fixture(params=["chrome", "firefox"])
def browser(request):
return start(request.param)
⚠️ Частая ошибка: путать области действия — засунуть в параметризованную фикстуру то, что нужно одному тесту, и нечаянно размножить весь модуль в 2-3 раза.
05В чём разница между assert и verify (soft assert)?
junior
Короткий ответ: Обычный assert останавливает тест на первом же несовпадении. Soft assert (verify) копит несоответствия и репортит их все в конце — полезно, когда надо проверить несколько независимых полей одной формы за один прогон.
Подробно:
- Hard assert — падение сразу, остальные проверки не выполняются (fail-fast).
- Soft assert — проверки накапливаются, тест падает в конце с полным списком; в TestNG —
SoftAssert+ обязательныйassertAll(). - Когда soft — независимые проверки (валидация всех полей формы): один прогон — вся картина сразу.
| Hard assert | Soft assert | |
|---|---|---|
| При несовпадении | стоп сразу | продолжает |
| Отчёт | первая ошибка | все ошибки |
| Когда | зависимые шаги | независимые проверки |
⚠️ Частая ошибка: в TestNG забыть вызвать assertAll() — SoftAssert без него не свалит тест, все накопленные падения молча теряются, и зелёный прогон скрывает баги.
06Чем stub отличается от mock?
middle
Короткий ответ: Stub возвращает заранее заготовленные ответы и ничего не проверяет — это подмена данных. Mock дополнительно верифицирует взаимодействия: какие методы вызвали, сколько раз и с какими аргументами — это подмена + проверка поведения.
Подробно:
- Stub — state verification: подсунули ответ, проверяем итоговый результат.
- Mock — behavior verification: проверяем сам факт и характер вызова (
assert_called_once_with). - Родня — fake (рабочая упрощённая реализация, напр. in-memory БД) и spy (обёртка, записывающая вызовы).
from unittest.mock import Mock
stub = Mock(); stub.get_rate.return_value = 1.1 # stub: только ответ
rate = converter.convert(100, stub)
mock = Mock() # mock: проверяем вызов
notifier.notify(mock, "hi")
mock.send.assert_called_once_with("hi")
| Stub | Mock | |
|---|---|---|
| Задача | отдать данные | проверить вызов |
| Проверяет | результат (state) | взаимодействие (behavior) |
| Провал теста | по assert результата | по неверному вызову |
⚠️ Частая ошибка: употреблять «мок» и «стаб» как синонимы. На собеседовании важно показать, что mock проверяет взаимодействие, а stub — только отдаёт данные.
07Как автотесты встроены в CI/CD-пайплайн? Что происходит, когда тест падает?
middle
Короткий ответ: Автотесты вешаются на триггер (push/PR) и бегут ступенями от быстрых к медленным: unit → API/smoke → регрессия. Отчёт (Allure) публикуется артефактом. Красный критичный тест — это gate: merge или деплой блокируется.
Подробно:
- Триггер — push, pull request, по расписанию (nightly) или ручной запуск.
- Ступени по цене — сначала быстрые unit, затем API/smoke, в конце тяжёлая UI-регрессия; падение на дешёвой ступени экономит время.
- Отчётность — Allure или JUnit XML как артефакт прогона, ссылка прямо в PR.
- Реакция на падение — критичный тест красный → pipeline красный → gate на merge; известные флаки уводят в карантин, а не блокируют всё.
push / PR
│
▼
[ unit ] → [ API / smoke ] → [ UI-регрессия ]
│ │ │
└── fail ─────┴────── fail ──────┘
▼
красный pipeline → merge заблокирован
▼
Allure-отчёт (артефакт)
⚠️ Частая ошибка: отвечать расплывчато «мы гоняем тесты в CI» без ступеней и без реакции на падение. Интервьюер ждёт gate на merge и внятную стратегию по флакам.
08Что такое Allure Report и чем он полезен?
junior
Короткий ответ: Allure — фреймворк отчётности, который превращает результаты прогона в наглядный HTML-отчёт с шагами, вложениями и трендом по истории. Публикуется как артефакт CI, чтобы команда видела не только «упало», но и где именно.
Подробно:
- @Step — разбивает тест на человекочитаемые шаги.
- @Attachment — прикрепляет скриншоты, логи, тело запроса/ответа к упавшему шагу.
- History trend — динамика pass/fail по прогонам, видно флаки и деградацию.
- Категории и severity — группировка дефектов и метки важности (critical/minor).
| Аннотация / фича | Зачем |
|---|---|
| @Step | шаги внутри теста |
| @Attachment | скриншоты / логи / HAR |
| @Severity | важность кейса |
| @Epic / @Feature / @Story | структура по функциональности |
| history trend | pass/fail по прогонам |
⚠️ Частая ошибка: генерировать Allure только локально и смотреть в одиночку. Ценность — публикация как CI-артефакта со ссылкой в PR и накопленной историей прогонов.
09Зачем Docker в инфраструктуре автотестов?
middle
Короткий ответ: Docker даёт воспроизводимые изолированные окружения для тестов: одинаковые версии браузера и драйвера, чистую одноразовую БД, Selenium Grid в контейнерах. Это убирает «у меня на машине работает» и часть флаков, вызванных различиями окружения.
Подробно:
- Одинаковые версии — образ фиксирует браузер + драйвер; локально и в CI одно и то же.
- Чистое состояние — одноразовый контейнер БД поднимается пустым на каждый прогон, тесты не тянут мусор от прошлых.
- Selenium Grid — hub и ноды браузеров в контейнерах, легко масштабировать параллель.
- Изоляция — тесты не гадят в хостовую систему;
docker compose down— и всё убрано.
services:
chrome:
image: selenium/standalone-chrome:148.0
shm_size: 2gb
ports: ["4444:4444"]
tests:
build: .
depends_on: [chrome]
environment:
REMOTE_URL: http://chrome:4444/wd/hub
⚠️ Частая ошибка: забыть про shm_size для контейнера с Chrome — браузер падает с «session deleted / tab crashed» из-за нехватки /dev/shm.
10Что такое contract testing (Pact) и что оно заменяет в микросервисах?
senior
Короткий ответ: Contract testing проверяет совместимость сервисов по контракту, а не через живое межсервисное окружение. В consumer-driven подходе (Pact) потребитель описывает ожидаемые запрос и ответ, провайдер обязан этот контракт выполнить; проверки бегут в CI на обеих сторонах и ловят breaking changes без поднятия всех сервисов.
Подробно:
- Consumer-driven — потребитель генерирует pact-файл из своих ожиданий к API.
- Provider verification — провайдер прогоняет контракт против себя в своём CI.
- Pact Broker — хранит контракты и версии;
can-i-deployразрешает или запрещает деплой. - Что заменяет — тяжёлые сквозные E2E между сервисами: быстрее, стабильнее, ловит рассинхрон API на раннем этапе.
┌──────────┐ ожидания ┌──────────┐ проверка ┌──────────┐
│ Consumer │ ──────────► │ Contract │ ◄────────── │ Provider │
│ (клиент) │ pact.json │ (Pact) │ verify в CI │ (сервис) │
└──────────┘ └────┬─────┘ └──────────┘
▼
Pact Broker
can-i-deploy? → gate
⚠️ Частая ошибка: считать contract testing заменой всем тестам. Он проверяет форму и совместимость API, но не бизнес-логику внутри сервиса — юнит- и функциональные тесты никуда не деваются.
11Как запустить тесты параллельно и зачем нужен ThreadLocal<WebDriver>?
middle
Короткий ответ: Параллель включается на уровне раннера: в TestNG — атрибуты parallel и thread-count в suite XML, в pytest — плагин pytest-xdist (-n auto). ThreadLocal<WebDriver> нужен, чтобы у каждого потока был свой инстанс драйвера: общий static-драйвер потоки затирают друг у друга, и тесты валятся вперемешку.
Подробно:
- TestNG —
<suite parallel="methods" thread-count="4">. - pytest —
pytest -n auto(xdist) распределяет тесты по воркерам. - Зачем ThreadLocal — изолирует драйвер по потоку; поток A не открывает URL в браузере потока B.
- Teardown —
driver.quit()иremove()из ThreadLocal, иначе течёт память и висят браузеры.
public class DriverFactory {
private static final ThreadLocal<WebDriver> TL = new ThreadLocal<>();
public static WebDriver get() {
if (TL.get() == null) TL.set(new ChromeDriver());
return TL.get(); // свой драйвер на поток
}
public static void quit() {
TL.get().quit();
TL.remove(); // иначе утечёт при пуле потоков
}
}
⚠️ Частая ошибка: держать один static WebDriver на все потоки. При параллели потоки дерутся за один браузер — рандомные падения, которые невозможно воспроизвести локально в один поток.
12Спроектируйте фреймворк автоматизации с нуля: какие слои выделите?
senior
Короткий ответ: Фреймворк раскладывается на слои с одной ответственностью каждый: конфиг и фабрика драйвера → page objects → steps/бизнес-действия → тесты, с тест-данными и отчётностью отдельными слоями и интеграцией в CI. Ключевое — SOLID: страница не знает про тест (SRP), драйвер пробрасывается через конструктор (DI), а не берётся из глобала.
Подробно:
- Config + фабрика драйвера — окружения, URL, ThreadLocal-драйвер.
- Page Objects — локаторы и действия страниц.
- Steps / бизнес-действия — сценарные шаги поверх страниц.
- Тесты — только «что проверяем» и ассерты.
- Тест-данные — отдельный слой (фабрики/билдеры, внешние файлы).
- Отчётность + CI — Allure и запуск в пайплайне.
┌──────────────────────────┐
│ CI / раннер (pipeline) │
├──────────────────────────┤
│ Отчётность (Allure) │
├──────────────────────────┤
│ Тесты (assert, «что») │
├──────────────────────────┤
│ Steps / бизнес-действия │
├──────────────────────────┤
│ Page Objects (локаторы) │
├──────────────────────────┤
│ Config + DriverFactory │
└──────────────────────────┘
Тест-данные — сквозной слой
⚠️ Частая ошибка: монолитный тест-класс, где локаторы, драйвер и ассерты в одной куче. Меняется вёрстка — правишь десятки тестов; это нарушение SRP и смерть поддерживаемости.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.