static принадлежит классу, а не конкретному объекту. Статические методы и поля существуют в единственном экземпляре, не зависящем от создания объекта.
Подходит для утилит, констант и фабричных методов, которые не зависят от состояния экземпляра.
Из практики: WebDriver instance часто делают static в JUnit-классах — это одна из причин гонок при параллельном запуске. Для изоляции лучше использовать ThreadLocal<WebDriver>.
Частые ошибки
Хранят изменяемое глобальное состояние в static.
Дополнительные вопросы
Чем опасен static в параллельных тестах?
В чём разница между equals и == (Java)?
Junior
`==` для примитивов (int, char, boolean) сравнивает **значения**. Для объектов `==` сравнивает **ссылки** — указывают ли переменные на один и тот же объект в памяти. `equals()` — смысловое равенство по правилам класса.
Для строк и value-объектов всегда используют `equals()`: `new String("abc") == new String("abc")` вернёт `false` (разные объекты), а `new String("abc").equals(new String("abc"))` — `true`.
Из практики: в автотестах `==` даёт «плавающие» падения — на литералах из пула строк `"abc" == "abc"` бывает `true` из-за string interning, а на объектах, созданных через `new`, — `false`.
Частые ошибки
Сравнивают объекты через ==.
Дополнительные вопросы
Почему equals и hashCode должны идти парой?
Чем отличаются примитивные и ссылочные типы в Java?
Junior
В Java есть примитивные типы (например, int, long, boolean) и ссылочные типы (классы, массивы, строки, коллекции).
Примитивы хранят само значение, а ссылочные типы хранят ссылку на объект в памяти.
Из практики: понимание этой разницы помогает избежать ошибок с null и сравнением объектов в тестовом коде.
Частые ошибки
Путают размеры примитивов (например, int vs long).
Неправильное сравнение строк через ==.
Дополнительные вопросы
Когда использовать StringBuilder vs StringBuffer?
Как работает String pool?
В чём разница между Maven и Gradle для Java тест-проектов?
Middle
Maven использует XML (pom.xml) с жёсткими конвенциями — просто и предсказуемо, хорошо для стандартных проектов. Gradle использует Groovy/Kotlin DSL — более гибкий и мощный, но требует более глубокого понимания.
Gradle быстрее Maven за счёт инкрементальных сборок и build cache — повторно пересобирает только изменённые модули. Maven пересобирает всё согласно lifecycle.
Из практики: для большинства тестовых проектов Maven проще поддерживать команде. Gradle беру, когда важна скорость сборки в монорепо или нужна нестандартная логика.
Частые ошибки
Конфликты зависимостей без dependency management.
Медленные builds без кэширования.
Дополнительные вопросы
Предпочитаете Maven или Gradle?
Как решаете dependency conflicts?
Как устроены основные коллекции в Java и когда какую использовать?
Middle
Основные коллекции: ArrayList — список с быстрым доступом по индексу O(1), LinkedList — быстрые вставки/удаления. HashMap — словарь с O(1) доступом по ключу, LinkedHashMap — с сохранением порядка. HashSet — уникальные элементы, TreeSet — уникальные в сортированном порядке.
Выбор по задаче: нужен порядок → ArrayList/LinkedList; нужна уникальность → HashSet; нужен поиск по ключу → HashMap; нужна очередь → ArrayDeque.
Из практики: правильный выбор коллекции сильно влияет на читаемость и скорость кода тестов — использую Map для тест-данных по ключу и Set для проверки уникальности ответов.
Частые ошибки
Неправильный выбор коллекции для задачи.
Забывают переопределить equals/hashCode.
Дополнительные вопросы
Внутренняя реализация HashMap?
Когда использовать ConcurrentHashMap?
Как устроены исключения в Java и как вы их обрабатываете?
Middle
Иерархия: всё наследуется от Throwable → Error (фатальные, не ловим) и Exception; внутри Exception выделяют RuntimeException и его наследников.
Checked (проверяемые) обязаны быть объявлены в throws или обработаны, unchecked (RuntimeException) — нет; это часть контракта метода.
Механизм: try/catch/finally для перехвата и гарантированной очистки (или try-with-resources), throw — выброс, throws — объявление; можно создавать свои классы-наследники Exception/RuntimeException.
Из практики: плохая обработка исключений часто делает тесты «зелёными», хотя бизнес-ошибка реально произошла.
Частые ошибки
Проглатывание исключений (пустой catch).
Неправильное использование finally.
Дополнительные вопросы
Когда создавать checked vs unchecked exception?
Как работает suppressed exceptions?
В чём разница между JUnit vs TestNG: сравнение Java testing frameworks?
TestNG даёт из коробки: группировка тестов (@Test(groups=...)), зависимости между тестами (@Test(dependsOnMethods=...)), параллельный запуск на уровне конфигурации (testng.xml), data providers (@DataProvider). Широко используется в Selenium-проектах и legacy корпоративных кодовых базах.
Из практики: для новых проектов беру JUnit 5 — проще и лучше поддерживается сообществом. TestNG выбираю, когда нужны сложные зависимости между тестами или проект уже на Selenium Grid с testng.xml. Принципиальная разница: JUnit-тесты независимы по дизайну, TestNG допускает явные зависимости между тестами.
Частые ошибки
Смешивание JUnit 4 и JUnit 5 annotations.
Неправильное использование dependencies в TestNG.
Дополнительные вопросы
Опыт с JUnit 5 возможности?
Как организуете parallel execution в TestNG?
Как вы используете Mockito для моков и заглушек в Java-тестах?
Middle
Mockito позволяет создавать моки через mock() или аннотацию @Mock с @ExtendWith(MockitoExtension.class). Поведение задаю через when(mock.method()).thenReturn(value) или doReturn(value).when(mock).method().
Проверяю взаимодействия через verify(mock).method(args) — был ли метод вызван и с какими аргументами. Для частичных моков использую spy(), для ArgumentCaptor — захватываю аргументы вызова.
Из практики: подменяю только то, что реально мешает тесту. Избыточные моки делают тесты хрупкими и слабо связанными с реальным поведением.
Частые ошибки
Over-mocking вместо интеграция тестов.
Неправильное использование argument matchers.
Дополнительные вопросы
Когда используете @Mock vs @Spy?
Опыт с PowerMock для static methods?
Как вы применяете принципы ООП в Java на практике?
Middle
Инкапсуляция в Java: приватные поля с геттерами/сеттерами, управление доступом через private/protected/public. Наследование: `extends` для классов (одиночное), `implements` для интерфейсов (множественное). `final class` запрещает наследование.
Полиморфизм: переопределение методов (@Override) и перегрузка. `instanceof` для проверки типа. Абстракция: `abstract class` (может иметь реализацию и состояние) vs `interface` (с Java 8 допускает default-методы). Для поведенческих контрактов без состояния — интерфейс, для базовой реализации — абстрактный класс.
Из практики: в автотестах применяю все четыре принципа постоянно — BasePage как абстрактный класс (инкапсуляция локаторов, базовое поведение), конкретные PageObject наследуют от него, WebDriver передаётся через конструктор (инкапсуляция). Стратегии авторизации — разные реализации одного интерфейса (полиморфизм).
Частые ошибки
Злоупотребление наследованием вместо композиции.
Нарушение принципов SOLID.
Дополнительные вопросы
Почему в Java нет множественного наследования классов?
Когда использовать абстрактный класс vs интерфейс?
Чем отличаются Jackson и Gson и что выбрать в Java-проекте?
Middle
Jackson — стандарт в Java-экосистеме, богатый API: ObjectMapper, аннотации (@JsonProperty, @JsonIgnore), поддержка полиморфизма, отличная интеграция со Spring. Производительность выше, чем у Gson.
Gson — простая библиотека от Google, минимум настроек, удобна для небольших проектов без Spring. Меньше аннотаций из коробки, хуже поддерживает сложные типы.
Из практики: в Spring-проектах Jackson уже включён по умолчанию, поэтому выбор очевиден. Gson подходит для небольших утилит или Android, где нет Spring-зависимостей.
Частые ошибки
Игнорирование null handling strategy.
Circular dependencies в моделях.
Дополнительные вопросы
Jackson vs GSON - когда что выбираете?
Опыт с custom serializers?
Как вы используете assertions, assumptions и timeout в JUnit 5?
Middle
Основные методы Assertions: `assertEquals(expected, actual, message)`, `assertThrows(Exception.class, () -> code)`, `assertAll(()-> assert1, ()-> assert2)` — группирует несколько проверок без остановки на первой ошибке.
Assumptions: `assumeTrue(condition)` — пропускает тест если условие не выполнено (не падает, а SKIPPED). Таймаут: `assertTimeout(Duration.ofSeconds(2), () -> code)` — защита от зависания.
Из практики: `assertAll` незаменим когда нужно проверить несколько полей объекта — вижу все расхождения сразу, а не останавливаюсь на первом несовпадении.
Частые ошибки
assertTimeoutPreemptively с thread-local variables.
Отсутствие message в assertions.
Дополнительные вопросы
Как выбираете между assertions/assumptions?
Опыт с custom assertion libraries?
Какие базовые аннотации JUnit 5 важно знать?
Middle
Основные аннотации JUnit 5: @Test — метод тест, @BeforeEach / @AfterEach — до/после каждого теста, @BeforeAll / @AfterAll — один раз для всего класса (должны быть static).
Дополнительные: @DisplayName — читаемое имя теста в отчёте, @Disabled — пропуск теста с причиной, @Tag — группировка для избирательного запуска, @Nested — вложенные классы тестов.
Из практики: @DisplayName сильно помогает в отчётах — вместо testUserCannotLoginWithWrongPassword вижу «Пользователь не может войти с неверным паролем».
Частые ошибки
Смешивание JUnit 4 и 5 аннотаций.
Неправильное использование static для PER_CLASS.
Дополнительные вопросы
Как мигрировали с JUnit 4 на 5?
Опыт с custom test engines?
Как вы применяете аннотации жизненного цикла в JUnit 5?
Middle
Аннотации жизненного цикла в JUnit 5: @BeforeAll — выполняется один раз перед всеми тестами класса (static-метод); @BeforeEach — перед каждым тестом; @AfterEach — после каждого теста; @AfterAll — один раз после всех тестов (static).
Разделяю общую подготовку (@BeforeAll для тяжёлых ресурсов: запуск контейнера, соединение с БД) и подготовку на каждый тест (@BeforeEach для состояния и данных) — это сохраняет изоляцию.
Из практики: корректный жизненный цикл убирает большинство случайных падений в серии запусков — каждый тест начинается с предсказуемого чистого состояния.
Частые ошибки
Забывают про static для @BeforeAll/@AfterAll.
Слишком много логики в setup/teardown.
Неправильный порядок очистки ресурсов.
Дополнительные вопросы
Как работает @TestInstance(Lifecycle.PER_CLASS)?
Опыт с вложенными тестами и lifecycle?
Какие практические примеры использования lifecycle-аннотаций в JUnit 5?
Middle
Типичный сценарий: в @BeforeAll поднимаем общий ресурс, в @BeforeEach готовим данные для конкретного теста, в @AfterEach чистим состояние.
Для API-тестов это часто выглядит как создание тестовых данных перед тестом и удаление после него, чтобы тесты не влияли друг на друга.
Из практики: правильный жизненный цикл резко снижает нестабильные падения в серии запусков.
Частые ошибки
Отсутствие очистки в случае ошибок.
Зависимости между тестами через состояние.
Избыточные действия в setup/teardown.
Дополнительные вопросы
Как организовать параллельное выполнение?
Стратегии работы с тестовыми данными?
Как вы используете параметризованные тесты в JUnit 5?
Middle
Параметризованные тесты создаются аннотацией @ParameterizedTest. Источники данных: @ValueSource (набор примитивов), @CsvSource (строки с несколькими значениями), @MethodSource (метод возвращает Stream), @EnumSource (все значения enum).
Повторяемые тесты — @RepeatedTest(n) — для нестабильных сценариев, но это не замена исправлению причины нестабильности.
Из практики: @CsvSource удобен для быстрого покрытия граничных значений прямо в коде теста, @MethodSource — когда данные сложные или читаются из файла.
Частые ошибки
Сложные @MethodSource без документации.
Избыточная параметризация простых тестов.
Дополнительные вопросы
Как организуете large test datasets?
Опыт с custom parameter resolvers?
Как вы применяете Maven: структура проекта, lifecycle и pom.xml?
Middle
Maven lifecycle состоит из фаз: validate → compile → test → package → verify → install → deploy. Каждая фаза включает предыдущие: `mvn test` выполнит validate, compile и test.
pom.xml содержит: groupId/artifactId/version (идентификатор), dependencies (зависимости), build/plugins (плагины), profiles (конфигурации для разных окружений). Структура проекта: src/main/java, src/test/java, src/test/resources.
Из практики: `mvn clean test -Dtest=LoginTest` — запуск одного теста без сборки всего; `mvn verify -P integration-tests` — интеграционные тесты через профиль. Знание команд экономит время при отладке CI.
Частые ошибки
Dependency version conflicts (version hell).
Неправильные dependency scopes.
Дополнительные вопросы
Как решаете dependency conflicts?
Опыт с custom lifecycle bindings?
Как вы применяете Maven для тестирования: plugins, profiles, CI/CD?
Middle
Maven для тестирования подключается через плагины: surefire запускает unit-тесты, failsafe — интеграционные, jacoco собирает покрытие.
Профили (profiles) позволяют запускать разные наборы тестов для разных окружений: mvn test -P integration, mvn test -P smoke.
Из практики: в CI передаю параметры через -D (mvn test -Denv=staging); сам failsafe отрабатывает на фазах integration-test и verify (verify проваливает сборку при упавших тестах), а старт и остановку окружения вешаю на pre-/post-integration-test отдельными плагинами (docker-maven-plugin, exec).
Частые ошибки
Медленный build из-за избыточных тестов.
Отсутствие test categorization.
Дополнительные вопросы
Как организуете test suites?
Опыт с custom Maven plugins?
Как вы применяете Rest Assured: фильтры и их применение?
Middle
Фильтр — это реализация интерфейса Filter: в методе filter() я перехватываю каждый запрос/ответ, чтобы централизованно логировать, собирать метрики или добавлять заголовки/авторизацию.
Готовые RequestLoggingFilter и ResponseLoggingFilter (или AllureRestAssured для отчётов) подключаю через given().filter(...) либо глобально RestAssured.filters(...).
Из практики: свой фильтр логирует тело запроса/ответа только при падении — причина видна сразу, без дублирования технического кода в каждом тесте.
Частые ошибки
Избыточное логирование в проде.
Неправильный порядок фильтров.
Дополнительные вопросы
Как реализовать retry механизм?
Опыт с custom error handling?
За что вы выбираете Selenide вместо Selenium?
Middle
Selenide упрощает интерфейсную автоматизацию за счёт удобных ожиданий и более лаконичного кода.
Я выбираю его, когда важно быстрее писать и поддерживать стабильные UI-проверки.
Из практики: меньше ручной работы с ожиданиями — меньше случайных падений.
Частые ошибки
Использование Thread.sleep вместо Selenide waits.
Игнорирование Configuration настроек в CI/CD.
Дополнительные вопросы
Когда предпочитаете pure Selenium?
Опыт с custom ElementsCollection matchers?
Как вы строите локаторы и действия с элементами в Selenide?
Middle
Локаторы строю через компактный $-синтаксис: $("#id") или $(By.cssSelector(...)) для одного элемента, $$(".row") — для коллекции, $x("//...") — для XPath.
Опираюсь на стабильные атрибуты и бизнес-смысл (data-testid, byText, byTagAndText), а не на хрупкие абсолютные XPath.
Действия пишу цепочкой на найденном элементе: $(...).click(), .setValue(...), .selectOption(...) — короткие читаемые шаги с ленивым поиском и авто-ожиданием.
Частые ошибки
Сложные XPath вместо CSS селекторов.
Отсутствие проверок перед действиями.
Дополнительные вопросы
Как работаете с Shadow DOM?
Опыт с custom selectors?
Как вы настраиваете ожидания и проверки в Selenide?
Middle
Ожидания в Selenide встроены в проверки: $(...).shouldBe(visible), shouldHave(text("...")), shouldNotBe(enabled) сами ждут выполнения условия, поэтому Thread.sleep не нужен.
Условия беру из класса Condition (visible, exist, text, value, enabled/clickable) — они описывают ожидаемое состояние элемента.
Глобальные параметры настраиваю через Configuration.timeout и Configuration.pollingInterval, а не жёсткие паузы — это главный фактор стабильности UI-регрессии в CI.
Частые ошибки
Избыточные явные ожидания.
Неиспользование collection operations.
Дополнительные вопросы
Как реализуете custom conditions?
Опыт с SoftAsserts в Selenide?
Как вы пишете API-тесты на Java с Rest Assured?
Middle
Строю тест в DSL Rest Assured: given() — baseURI, заголовки, тело и авторизация; when() — HTTP-метод и путь; then() — проверки.
В then() проверяю statusCode() и тело через body(...) с matchers (Hamcrest/JSONPath); нужные значения достаю через extract() для следующих шагов.
Базовые настройки и повторяющиеся проверки выношу в RequestSpecification/ResponseSpecification и отдельные методы для читаемости.
Из практики: такой формат помогает быстро локализовать поломку после изменений API.
Частые ошибки
Хардкод URLs и данных в тестах.
Игнорирование response time validation.
Дополнительные вопросы
Как организуете test данные management?
Опыт с JSON Schema validation?
Что важно знать QA про многопоточность в Java?
Senior
Ключевые механизмы Java: synchronized, volatile, happens-before и пакет java.util.concurrent (атомики, ConcurrentHashMap, ExecutorService) — понимаю, чем защищён общий стейт.
Целенаправленно проверяю гонки (race condition), взаимные блокировки (deadlock), livelock и утечки/исчерпание потоков в пулах.
Инструменты: стресс-прогоны с повторяемыми сценариями, thread dump / jstack и детекторы гонок для воспроизведения плавающих багов.
Из практики: такие дефекты редко ловятся с первого раза, поэтому нужны многократные прогоны, подробные журналы и контроль порядка выполнения.
Регистрация через `@ExtendWith(MyExtension.class)` на классе или через `ServiceLoader` для глобальной регистрации. Популярный пример: `@ExtendWith(MockitoExtension.class)`, `@ExtendWith(SpringExtension.class)`.
Из практики: создаю собственный extension для логирования времени выполнения каждого теста через `BeforeEachCallback` + `AfterEachCallback` — помогает находить медленные тесты без сторонних инструментов.
Частые ошибки
Слишком много extensions замедляют тесты.
Неправильный порядок extensions.
Дополнительные вопросы
Как реализуете custom extensions?
Опыт с parallel test execution?
Какие продвинутые возможности Selenide вы используете на практике?
Senior
Продвинутые возможности: кастомные условия через `Condition.and()` и собственные реализации `Condition`; работа с коллекциями через `$$.filter(visible).shouldHave(size(3))`; `SelenideElement.as('имя')` для читаемых ошибок.
Работа с iframe: `switchTo().frame()`, JavaScript execution: `Selenide.executeJavaScript()`, `WebDriverRunner.getWebDriver()` для прямого доступа к WebDriver, автоскриншоты через `Configuration.screenshots=true`.
Из практики: `CollectionCondition.texts('A', 'B', 'C')` — элегантная проверка порядка элементов в списке вместо перебора в цикле. Значительно сокращает код тестов на проверках динамических списков.
Частые ошибки
Неоптимальная стратегия page load.
Отсутствие cleanup в teardown.
Дополнительные вопросы
Как масштабируете Selenide tests?
Опыт с remote WebDriver setup?
Чем интерфейс отличается от абстрактного класса в Java?
Junior
Интерфейс описывает контракт без состояния (до Java 8 — только сигнатуры; начиная с 8 — default- и static-методы). Класс реализует несколько интерфейсов.
Абстрактный класс может иметь поля, конструкторы, реализованные методы. От него наследуются одним классом.
Из практики: интерфейсы для горизонтальных способностей (Comparable, Serializable). Абстрактный класс — когда есть общая база с состоянием и поведением.
Частые ошибки
Делать «жирный» интерфейс с десятком методов вместо composition.
Считать default-методы заменой абстрактного класса полностью.
Дополнительные вопросы
Что такое sealed классы и интерфейсы?
Можно ли в интерфейсе иметь private методы?
Что такое equals и hashCode и какой их контракт?
Middle
equals определяет логическое равенство. hashCode возвращает int для использования в hash-структурах (HashMap, HashSet).
Контракт: если a.equals(b), то a.hashCode() == b.hashCode(); обратное не обязательно. Если переопределили equals — обязательно переопределяйте hashCode.
Из практики: IDE генерирует оба автоматически. В record-классах они генерируются языком. Нарушение контракта ведёт к багам, которые сложно отловить.
Частые ошибки
Переопределить equals и забыть hashCode — объект «теряется» в HashMap.
Использовать в hashCode mutable поля — после изменения объект пропадёт из множества.
Дополнительные вопросы
Что такое запись (record) в Java?
Чем Objects.equals удобен?
Что такое generics в Java и зачем они?
Middle
Generics — параметризованные типы: List<String>, Map<K, V>. Дают типобезопасность на этапе компиляции — нельзя положить Integer в List<String>.
Type erasure — после компиляции параметры стираются, в рантайме List<String> и List<Integer> неразличимы.
Из практики: используют ровно как в других языках, но помня про erasure — нельзя new T(), нельзя instanceof T.
Частые ошибки
Использовать raw type (List вместо List<...>) — теряется типобезопасность.
Игнорировать warning о unchecked operation.
Дополнительные вопросы
Что такое wildcard ?, extends, super?
Что такое PECS?
Что такое Optional и когда его использовать?
Middle
Optional<T> — обёртка, явно сигнализирующая «значение может отсутствовать». Методы: isPresent, get, orElse, orElseGet, map, ifPresent.
Используется как возвращаемый тип методов, которые могут не дать результат: Optional<User> findById(id).
Из практики: Optional не для полей объектов (увеличивает память, проблемы с сериализацией) и не для параметров. Только для возвращаемых типов методов.
Частые ошибки
Использовать Optional как поле — анти-паттерн.
Звать .get() без isPresent — выкинет NoSuchElementException.
Дополнительные вопросы
Чем orElse отличается от orElseGet?
Можно ли иметь Optional<Optional<T>>?
Что такое Stream API?
Middle
Stream API — функциональный способ обработки коллекций. Цепочка операций: filter, map, sorted, limit, collect. Терминальная операция запускает поток.
Streams ленивые — операции выполняются только при terminal operation. Промежуточные строят описание пайплайна.
Из практики: для функциональных преобразований, агрегаций, параллельной обработки (parallelStream). Не заменяет цикл, когда нужны побочные эффекты или сложная логика.
Частые ошибки
Использовать parallelStream без понимания — может быть медленнее.
Делать побочные эффекты в map — нарушает чистоту.
Дополнительные вопросы
Чем Stream.of отличается от Collection.stream?
Когда parallelStream даёт выгоду?
Что такое try-with-resources?
Junior
Конструкция try (Resource r = new Resource()) {...} автоматически закроет r после блока (вызовом close()). Работает с любым AutoCloseable.
Сравнима с with в Python — гарантирует освобождение ресурса даже при исключении. Можно объявить несколько ресурсов через ;.
Из практики: всегда используйте для файлов, соединений, потоков. Это резко снижает шанс утечек ресурсов.
Частые ошибки
Открывать ресурсы вне try-with-resources и забывать закрывать.
Полагаться, что close выкинет понятное исключение — оно может быть suppressed.
Дополнительные вопросы
Что такое suppressed exception?
Можно ли использовать уже инициализированный ресурс?
Что такое checked и unchecked исключения?
Junior
Checked — наследники Exception (не RuntimeException). Компилятор требует обрабатывать или declare через throws.
Unchecked — RuntimeException и потомки. Не требуют объявления, обычно сигнализируют о баге программы (NullPointerException, IllegalArgumentException).
Из практики: checked удобны для recoverable-ситуаций (IOException, SQLException). Unchecked — для программных ошибок. Тренд — больше unchecked: они меньше засоряют сигнатуры.
Частые ошибки
Ловить Exception целиком — глушит и unchecked, и checked.
Бросать RuntimeException там, где явно ожидается recovery.
Дополнительные вопросы
Чем Error отличается от Exception?
Что такое try/catch/finally?
Что такое volatile и synchronized?
Middle
volatile у поля гарантирует видимость изменений другими потоками и упорядоченность (happens-before). Без него поток может видеть «свою» копию значения.
synchronized — блок или метод, защищённый монитором объекта. В каждый момент только один поток внутри. Также даёт видимость памяти.
Из практики: volatile подходит для флагов и счётчиков с атомарным присваиванием. synchronized — для составных операций. Современный код часто использует Atomic-классы и Lock из java.util.concurrent.
Частые ошибки
Считать volatile достаточным для инкремента (++) — не атомарно.
Использовать synchronized(this) в публичном классе — снаружи кто-то может синхронизироваться на том же объекте.
Дополнительные вопросы
Что такое happens-before?
Чем ReentrantLock лучше synchronized?
Что такое функциональный интерфейс и lambda?
Middle
Функциональный интерфейс — интерфейс с одним абстрактным методом (@FunctionalInterface). Лямбда — компактный синтаксис анонимной реализации: list.forEach(x -> System.out.println(x)).
Стандартная библиотека: Function, Consumer, Supplier, Predicate, BiFunction. Доступны в java.util.function.
Из практики: используют со Stream API, для коллбэков, для DSL. Method reference (System.out::println) — ещё более лаконичная форма.
Частые ошибки
Капчурить mutable переменные в замыкании — нужно final или effectively final.
Делать лямбду на 30 строк — лучше выделить в метод.
Дополнительные вопросы
Чем method reference удобен?
Что такое effectively final?
Что такое CompletableFuture?
Middle
CompletableFuture<T> — асинхронный результат с возможностью композиции: thenApply, thenCompose, thenCombine, exceptionally, allOf, anyOf.
Запускают через CompletableFuture.supplyAsync(...) — выполняется на ForkJoinPool.commonPool по умолчанию или на переданном Executor.
Из практики: позволяет писать неблокирующий код без callback hell. Хорошо подходит для параллельных вызовов нескольких сервисов.
Частые ошибки
Использовать common pool для тяжёлых блокирующих операций — заблокируете весь pool.
ArrayList основан на массиве — O(1) индексный доступ, O(n) вставка/удаление в середину. LinkedList — двусвязный список — O(1) вставка/удаление, но O(n) индексный доступ.
На практике ArrayList почти всегда быстрее: компактнее в памяти, дружелюбен к кешу CPU.
Из практики: используйте ArrayList по умолчанию. LinkedList оправдан только когда вставка/удаление в начало — частая операция (как Deque).
Частые ошибки
Выбирать LinkedList «потому что вставки быстрые» без понимания кеша.
Использовать LinkedList с .get(i) в цикле — катастрофическая производительность.
Дополнительные вопросы
Что такое ArrayDeque и чем оно лучше LinkedList как Deque?
Какая сложность у add в ArrayList?
Чем HashMap отличается от LinkedHashMap и TreeMap?
Middle
HashMap — на хешах, O(1) операции, порядок не гарантирован. LinkedHashMap — хеш плюс linked list по порядку вставки (или access order). TreeMap — на red-black tree, O(log n), отсортирован по ключу.
TreeMap позволяет операции по диапазону (subMap, headMap, tailMap). LinkedHashMap часто используется как LRU-кеш.
Из практики: по умолчанию HashMap. Когда важен порядок — LinkedHashMap. Когда нужна сортировка/диапазон — TreeMap.
Частые ошибки
Класть mutable ключи в HashMap и менять их — поломка структуры.
Использовать null-ключ в TreeMap — NullPointerException.
Дополнительные вопросы
Что такое access-order LinkedHashMap?
Как реализовать LRU кеш?
Что такое record-класс?
Middle
record — это компактная форма для иммутабельных носителей данных. record User(String name, int age) {} автоматически даёт final-поля, конструктор, equals/hashCode, toString, аксессоры.
Аналог data class в Kotlin. Доступен с Java 14 (preview), стабилен с Java 16.
Из практики: для DTO, value-объектов — идеально. Уменьшает шаблонный код. Можно добавлять методы и compact-конструктор с валидацией.
Частые ошибки
Делать record для сущности с identity — равенство по значению вводит в заблуждение.
Считать record полной заменой class — есть ограничения (нельзя наследоваться).
Дополнительные вопросы
Что такое sealed класс?
Можно ли у record добавить методы?
Что такое sealed классы?
Middle
sealed class/interface ограничивает набор подтипов: указывают permits A, B, C. Только эти классы могут наследоваться. Доступно с Java 17.
Полезно для замкнутых иерархий: Result = Success | Error. Компилятор знает все варианты — pattern matching в switch покрывает их exhaustive.
Из практики: помогает выражать ADT (algebraic data types) в Java. Хорошо сочетается с record и pattern matching.
Частые ошибки
Считать, что sealed предотвращает изменения в runtime — он на уровне компиляции.
Делать sealed во вложенных пакетах без правильного импорта.
Дополнительные вопросы
Чем sealed отличается от final?
Что такое pattern matching for switch?
Что такое Garbage Collector в JVM и какие виды?
Middle
GC — автоматический сборщик мусора JVM. Освобождает память от объектов, на которые нет ссылок. В современной JVM есть несколько GC: Serial, Parallel, G1 (по умолчанию с Java 9), ZGC, Shenandoah.
Различаются стратегиями: пауза (stop-the-world) vs throughput vs latency. G1 — баланс. ZGC и Shenandoah — низкая пауза (<10мс) ценой пропускной способности.
Из практики: для большинства приложений хватает G1 по умолчанию. ZGC выбирают, когда критична latency (трейдинг, real-time). Настройка GC — это про размер heap, young/old generation и пороги.
Частые ошибки
Тюнить GC без бенчмарков и метрик — ставит хуже.
Считать, что GC решает все проблемы памяти.
Дополнительные вопросы
Что такое stop-the-world?
Когда выбирать ZGC?
Что такое Selenide и чем он отличается от чистого Selenium?
Junior
Selenide — это обёртка над Selenium для Java, ориентированная на тесты. Лаконичный синтаксис, авто-ожидания, удобные ассерты, скриншоты при падении из коробки.
Selenium предоставляет низкий уровень управления браузером. Selenide добавляет «удобный фасад» с предсказуемой стабильностью.
Из практики: для нового UI-проекта на Java часто выбирают Selenide. Selenium остаётся для кастомных сценариев и интеграций.
Частые ошибки
Думать, что Selenide заменяет Selenium — он использует его под капотом.
Игнорировать настройку Configuration.
Дополнительные вопросы
Что такое Configuration.timeout?
Когда Selenide не подходит?
Как работает $(...) в Selenide?
Junior
$ — это короткая запись для Selenide.$, возвращает ленивый SelenideElement. Он не ищется сразу, а в момент действия (click, sendKeys, shouldBe).
Поддерживает CSS и Selenide-расширения: $(byText("...")), $(byTitle("...")), $(byAttribute(...)). Также $$ для коллекций.
Из практики: благодаря лениой природе $-локаторы стабильнее — auto-wait срабатывает при действии. Не нужно явных WebDriverWait.
Частые ошибки
Сохранять SelenideElement и потом удивляться, что StaleElementReference: лучше пересоздавать.
Использовать сложные XPath, когда есть by-методы.
Дополнительные вопросы
Что такое byText и когда оно полезно?
Как использовать $$ с фильтрами?
Что такое условие should/shouldBe и зачем оно?
Junior
should/shouldBe/shouldHave — это ретраящиеся ассерты, ждущие выполнения условия в течение Configuration.timeout. $(element).shouldBe(visible), .shouldHave(text(...)).
Это и есть основной механизм авто-ожидания в Selenide. Аналог expect(locator).toBeVisible() в Playwright.
Из практики: пишите ассерты на должное состояние, а не fixed sleep. Selenide сам подождёт нужное и упадёт с понятной ошибкой, если не дождался.
Частые ошибки
Использовать Selenide-условия с обычным assertEquals — теряется ретрай.
Завышать timeout — тесты медленные.
Дополнительные вопросы
Чем shouldBe отличается от shouldHave?
Какие условия встроены?
Как Selenide делает скриншоты при падении?
Junior
По умолчанию Selenide сохраняет скриншот и HTML страницы в build/reports/tests при падении теста. Путь настраивается через Configuration.reportsFolder.
Для JUnit/TestNG есть готовые listener-ы, прикладывающие артефакты в отчёт (Allure).
Из практики: эта функция «бесплатно» закрывает 90% запросов «дайте картинку, что было на экране перед падением».
Частые ошибки
Не настроить Allure listener и иметь скриншоты «в стороне».
Драйверы скачиваются автоматически через WebDriverManager. Для удалённого запуска — Configuration.remote = "http://selenium-grid:4444/wd/hub".
Из практики: настройку обычно делают через System.properties или application.properties. Это позволяет менять окружение без правки кода.
Частые ошибки
Хардкодить browser в коде — мешает запускать на разных средах.
Не настраивать timeout и удивляться flaky.
Дополнительные вопросы
Что такое WebDriverManager?
Как запустить Selenide в Docker?
Что такое page object в Selenide?
Middle
Класс на страницу, содержащий локаторы как поля и методы действий. Часто аннотируют поля @FindBy или объявляют через SelenideElement.
page = Selenide.page(LoginPage.class) инициализирует все поля как SelenideElement.
Из практики: помогает декомпозировать тесты и снизить дублирование. Не превращайте POM в анемичный набор геттеров — добавляйте методы action.
Частые ошибки
Прятать в page object ассерты — теряется явность теста.
Делать POM на каждый компонент — оверкилл.
Дополнительные вопросы
Чем @FindBy отличается от $-локатора?
Что такое component object?
Что такое soft assertions в Selenide?
Middle
Обычные shouldBe сразу падают при ошибке. Soft assertions собирают все ошибки и кидают одну итоговую — удобно, когда хочется проверить несколько вещей за один проход.
Включается через Configuration.assertionMode = SOFT и SelenideLogger.addListener.
Из практики: помогает в UI-тестах с множеством мелких проверок. Минус — длиннее лог, иногда сложнее найти первопричину.
Частые ошибки
Использовать soft assertions везде — теряется fail-fast.
Не настраивать SelenideLogger — soft не сработает.
Дополнительные вопросы
Чем soft assertions отличаются от @SoftAssertions в AssertJ?
Когда лучше hard, а когда soft?
Как протестировать загрузку и скачивание файла в Selenide?
Middle
Загрузка: $("input[type=file]").uploadFile(new File("path")). Selenide подставит файл через DOM, не открывая диалог.
Скачивание: File f = $("a.download").download(); — Selenide перехватит скачивание и вернёт сохранённый файл. Поддерживает несколько режимов: PROXY, FOLDER, HTTPGET.
Из практики: для скачивания через ссылки часто стабильнее HTTPGET — Selenide дёргает URL сам. PROXY-режим работает с любой кнопкой, но настраивается сложнее.
Частые ошибки
Не настраивать FileDownloadMode и удивляться, что download не работает.
Хранить тестовые файлы в неправильных путях.
Дополнительные вопросы
Что такое FileDownloadMode.PROXY?
Чем uploadFile отличается от sendKeys?
Что такое holdBrowserOpen и зачем оно?
Middle
Configuration.holdBrowserOpen = true оставляет браузер открытым после завершения теста. Полезно для отладки — посмотреть финальное состояние страницы.
Также есть Configuration.reopenBrowserOnFail для пересоздания драйвера при потере.
Из практики: holdBrowserOpen — только локально. В CI это превратит сборку в зомби с десятками открытых браузеров.
Частые ошибки
Закоммитить holdBrowserOpen = true в CI.
Полагаться, что страница не изменится после теста — она может догрузить асинхронно.
Дополнительные вопросы
Как настроить headless = false для отладки?
Чем reopenBrowserOnFail полезен?
Как Selenide работает с iframe?
Middle
Selenide.switchTo().frame("name") или Selenide.switchTo().frame($("iframe#x")) переключают контекст. После работы выходят через switchTo().defaultContent() или switchTo().parentFrame().
Все последующие $-локаторы работают внутри iframe до выхода.
Из практики: типовая ошибка — забыть выйти из iframe и удивляться, почему не находятся элементы за его пределами. Полезно обернуть в try/finally или вспомогательный метод inFrame(...).
Частые ошибки
Забывать switchTo().defaultContent().
Полагаться, что $(...) автоматически проникнет в iframe — нет.
Дополнительные вопросы
Как написать вспомогательный inFrame(action)?
Чем switchTo().frame отличается от parentFrame()?
Чем JUnit 5 отличается от JUnit 4?
Middle
JUnit 5 — модульная архитектура (Jupiter, Vintage, Platform). Современные возможности: @DisplayName, @Nested, @ParameterizedTest, расширения через Extension API, java.time, лямбды.
JUnit 4 — монолитный, аннотации проще (@Test, @Before, @After). Уже не развивается, поддерживается в legacy через Vintage.
Из практики: для нового проекта — JUnit 5. Миграция с 4 пошагово через Vintage Engine, который позволяет запускать оба одновременно.
Частые ошибки
Смешивать импорты org.junit и org.junit.jupiter.
Использовать @BeforeAll без static в JUnit 5 без @TestInstance(PER_CLASS).
Дополнительные вопросы
Что такое JUnit Platform?
Что даёт @Nested?
Какие основные аннотации JUnit 5?
Junior
@Test — это тестовый метод. @BeforeEach/@AfterEach — перед/после каждого теста. @BeforeAll/@AfterAll — один раз на класс (static или с PER_CLASS).
@DisplayName — человеко-читаемое имя. @Disabled — пропустить. @Tag — категоризация. @Timeout — лимит времени. @RepeatedTest, @ParameterizedTest — повтор и параметризация.
Из практики: для разных категорий тестов используют @Tag и фильтруют запуск (mvn test -Dgroups=smoke).
Частые ошибки
Использовать org.junit.Test (JUnit 4) вместо org.junit.jupiter.api.Test.
Полагаться, что @BeforeEach выполнится в порядке наследования — это так, но порядок методов в одном классе не гарантирован.
Дополнительные вопросы
Чем @RepeatedTest отличается от @ParameterizedTest?
Что делает @Order?
Что такое @ParameterizedTest и какие источники данных?
Middle
@ParameterizedTest запускает тест на разных входных данных. Источники: @ValueSource (примитивы и строки), @CsvSource, @CsvFileSource, @MethodSource (своя статическая фабрика), @EnumSource, @ArgumentsSource (полностью кастомный).
Имя теста настраивается шаблоном: @ParameterizedTest(name = "{0} → {1}").
Из практики: для проверки бизнес-логики на наборе кейсов — самый удобный способ. @CsvFileSource подходит, когда данных много или они приходят из тест-аналитика.
Частые ошибки
Класть в @CsvSource огромный объём — теряется читаемость.
Использовать @ValueSource там, где нужна более одной колонки.
Дополнительные вопросы
Что такое @MethodSource?
Чем @ArgumentsSource удобен?
Что такое Extension в JUnit 5?
Middle
Extension — механизм расширения JUnit 5 на смену Runner и Rule из JUnit 4. Регистрируется через @ExtendWith. Реализует callback-интерфейсы: BeforeAllCallback, AfterEachCallback, ParameterResolver, TestWatcher.
Применения: внедрить зависимости в параметры теста, обработать исключения, добавить логирование, временные ресурсы.
Из практики: библиотеки вроде Mockito (@ExtendWith(MockitoExtension.class)), SpringBoot (@SpringBootTest), Testcontainers — всё через Extension.
Частые ошибки
Применять @ExtendWith вручную там, где есть готовая мета-аннотация.
Делать тяжёлые BeforeAllCallback — замедление всех тестов класса.
Дополнительные вопросы
Что такое ParameterResolver?
Чем Extension лучше Rule/Runner?
Что такое @Nested и зачем оно?
Middle
@Nested — вложенные классы внутри теста, образующие иерархию. Помогает группировать тесты по контексту: @Nested class WhenLoggedIn { @Nested class AndAdmin {... }}.
Каждый уровень может иметь свои @BeforeEach и фикстуры — упрощает структуру shared-setup'ов.
Из практики: BDD-стиль — отчёты получаются как «when ... and ... then ...», понятно даже PM-у.
Частые ошибки
Создавать слишком глубокую вложенность — путаница.
Делать @Nested static — он должен быть inner class.
Дополнительные вопросы
Чем @Nested отличается от обычных классов?
Можно ли использовать @TestInstance(PER_CLASS) в @Nested?
Чем assertEquals отличается от assertThat?
Junior
assertEquals и assertTrue — встроенные ассерты JUnit. Просты, но при ошибке дают скудное сообщение.
assertThat — стиль из AssertJ или Hamcrest. assertThat(list).hasSize(3).contains("a"). Чейнинг, читаемые сообщения, тонкая семантика.
Из практики: для серьёзного проекта добавьте AssertJ. Сообщения об ошибках получаются информативные, тесты пишутся быстрее.
Частые ошибки
Смешивать assertThat из Hamcrest и AssertJ — несовместимые API.
Передавать lambda там, где нужно значение.
Дополнительные вопросы
Чем AssertJ лучше Hamcrest?
Как сделать кастомный assert?
Что такое @Timeout?
Middle
@Timeout задаёт максимальное время выполнения теста. Если превышено — fail. Доступно на тесте, классе и через assertTimeoutPreemptively.
Режимы threadMode: SAME_THREAD (в том же потоке), SEPARATE_THREAD (в отдельном потоке, можно прервать), INFERRED (по умолчанию — выводится из конфигурации).
Из практики: спасает от случайных deadlock'ов и зависаний — CI не висит часами. Но не лечит причину, только сигнализирует.
Частые ошибки
Ставить очень короткий timeout, без учёта CI-машины.
Использовать assertTimeoutPreemptively с операциями, которые нельзя прерывать.
Дополнительные вопросы
Чем @Timeout отличается от assertTimeout?
Что такое ThreadMode.SEPARATE_THREAD?
Что такое @Disabled и когда его применять?
Junior
@Disabled пропускает тест или класс. Лучше указывать reason: @Disabled("flaky, ticket #123") или @Disabled("awaiting feature toggle").
Также есть условные варианты: @DisabledIfEnvironmentVariable, @DisabledOnOs, @EnabledIfSystemProperty.
Из практики: правило — каждый @Disabled с причиной и сроком. Иначе превращается в долгое мёртвое поле в коде.
@TestFactory создаёт «динамические тесты» — Collection/Stream/Iterator из DynamicTest объектов. Каждый DynamicTest имеет имя и Executable.
В отличие от @ParameterizedTest, набор тестов вычисляется в runtime — например, из внешнего файла, через API.
Из практики: подходит для генерируемых тестов (например, BDD-сценарии из feature-файла). Менее распространено, чем parameterized, но мощно.
Частые ошибки
Считать @TestFactory заменой parameterized — у них разные сценарии.
Делать слишком много динамических тестов — отчёт раздувается.
Дополнительные вопросы
Чем @TestFactory отличается от @ParameterizedTest?
Что такое DynamicContainer?
Какие лучшие практики наименования тестов?
Junior
Имя должно отвечать на вопросы: что тестируем, при каких условиях, какой результат. Примеры: shouldReturnEmptyList_whenNoOrders, login_withInvalidPassword_returns401.
Также популярна BDD-форма given_when_then. Для @DisplayName можно писать предложением: «Login fails with invalid password».
Из практики: хорошее имя — это документация. Когда тест падает, по имени сразу понимаешь, что сломалось, без чтения кода.
Частые ошибки
Имена вроде test1, testFoo — никакой информации.
Длинные имена с подробностями реализации — устаревают быстро.
Дополнительные вопросы
Что лучше: @DisplayName или говорящее имя метода?
Чем BDD-стиль удобен?
Чем String отличается от StringBuilder?
Junior
String иммутабельный — каждая операция (+, replace) создаёт новый объект. StringBuilder — изменяемый буфер, можно эффективно конкатенировать в цикле.
Для одной-двух конкатенаций — String. Для цикла или сложной сборки — StringBuilder.
Из практики: компилятор оптимизирует a + b + c в StringBuilder автоматически. Но в цикле всё равно вручную.
Частые ошибки
Использовать String в цикле — N^2 аллокаций.
Использовать StringBuffer без необходимости — он thread-safe, медленнее.
Дополнительные вопросы
Чем StringBuilder отличается от StringBuffer?
Что такое String pool?
Что такое immutable объект и как его создать?
Middle
Immutable объект нельзя изменить после создания. Все поля final, нет setter'ов, в конструкторе копируем mutable аргументы.
Класс final (или конструктор приватный) — чтобы нельзя было наследоваться и изменить поведение. Geter'ы возвращают копии mutable полей.
Из практики: immutable объекты автоматически thread-safe, легко кешируются, безопасны для использования как ключи Map. String, Integer — классические примеры.
Частые ошибки
Считать класс immutable, имея mutable поле без копирования.
Возвращать ссылку на внутренний массив или коллекцию.
Дополнительные вопросы
Что такое defensive copy?
Чем record упрощает создание immutable?
Что такое try-finally vs try-with-resources?
Middle
try-finally — старая форма: вручную закрывать ресурсы в finally. Многословно, легко забыть, проблема с suppressed exceptions.
try-with-resources автоматически закроет AutoCloseable после блока, в правильном порядке. Уменьшает шаблонный код, безопаснее.
Из практики: для нового кода всегда try-with-resources. try-finally остаётся для случаев, когда ресурс не реализует AutoCloseable.
Частые ошибки
Закрывать ресурс в finally без проверки на null — может бросить NPE.
Не использовать try-with-resources для AutoCloseable.
Дополнительные вопросы
Что такое suppressed exception?
Можно ли иметь несколько ресурсов в try-with-resources?
Что такое equals и hashCode для коллекций?
Middle
HashMap, HashSet используют hashCode для бакета и equals для проверки. Если переопределили equals и не hashCode — объекты теряются в коллекциях.
Контракт: a.equals(b) => a.hashCode() == b.hashCode(). Обратное не обязательно.
Из практики: используйте Objects.hash(field1, field2, ...) для быстрой реализации. IDE автогенерирует. record делает это автоматически.
Частые ошибки
Переопределить только equals.
Использовать mutable поля в hashCode — после изменения объект пропадает из множества.
Дополнительные вопросы
Что произойдёт, если положить mutable объект в HashSet и потом изменить его?
Чем record упрощает equals/hashCode?
Что такое Comparable и Comparator?
Middle
Comparable<T> — интерфейс с методом compareTo. Реализуют сами классы, чтобы определить «естественный порядок».
Comparator<T> — отдельный объект, передаваемый в сортировку: Comparator.comparing(User::getName). Позволяет иметь несколько вариантов сортировки.
Из практики: для одного-единственного порядка — Comparable. Для разных сортировок (по имени, по возрасту, обратном) — Comparator.
Частые ошибки
Делать compareTo несогласованным с equals — Set ведёт себя странно.
Возвращать огромные числа в compareTo вместо -1/0/1.
Дополнительные вопросы
Чем Comparator.reversed() полезен?
Как сравнивать null-значения?
Что такое ConcurrentHashMap и почему он быстрее synchronized HashMap?
Middle
ConcurrentHashMap разрешает параллельные чтения без блокировок, а запись блокирует точечно — на уровне отдельного бакета/узла, а не всей карты.
synchronized Map блокирует всю коллекцию на каждой операции — под нагрузкой это узкое место.
Из практики: до Java 7 была сегментная блокировка (Segment), с Java 8 — CAS + synchronized на голове бакета. По умолчанию для многопоточного map берут ConcurrentHashMap; compute/computeIfAbsent атомарны.
Частые ошибки
Использовать putIfAbsent + get вместо computeIfAbsent — два хода вместо одного атомарного.
Класть null в ConcurrentHashMap — NPE (в отличие от HashMap).
Дополнительные вопросы
Что такое computeIfAbsent?
Можно ли итерировать ConcurrentHashMap во время изменения?
Что такое ThreadLocal и для чего он?
Middle
ThreadLocal<T> — это контейнер, у которого свой набор значений для каждого потока. Помогает избежать передачи параметров через много слоёв (тип контекста, текущий пользователь).
Минусы: легко создать утечку памяти (особенно в thread pool, где потоки переживают запрос), сложно отлаживать.
Из практики: типичный сценарий — request context в web-фреймворке. Очищать в finally обязательно (иначе следующий запрос получит чужие данные).
Частые ошибки
Не очищать ThreadLocal в пуле потоков — утечка.
Использовать ThreadLocal вместо явной передачи параметра — неявность.
Дополнительные вопросы
Что такое InheritableThreadLocal?
Чем ScopedValue (Java 21) лучше?
Что такое volatile и happens-before?
Middle
volatile гарантирует видимость записи в поле другим потокам. Java Memory Model говорит: запись в volatile happens-before чтения того же volatile.
Без volatile процессор может закешировать значение, и другой поток будет видеть старое.
Из практики: volatile подходит для флагов (running, shutdown), счётчиков с одиночной записью. Для compound-операций (i++) — AtomicInteger.
Частые ошибки
Использовать volatile для i++ — не атомарно.
Полагать, что volatile делает блок кода атомарным.
Дополнительные вопросы
Что такое CAS?
Чем AtomicInteger лучше volatile int?
Что такое ExecutorService?
Middle
ExecutorService — абстракция над пулом потоков. Создают через Executors.newFixedThreadPool(N), submit(Callable) возвращает Future, awaitTermination ждёт завершения.
Подвиды: fixed, cached, scheduled, virtualThreadExecutor (Java 21). Каждый под свои задачи.
Из практики: не создавайте сырые Thread в production-коде. ExecutorService даёт контроль над жизненным циклом и пулом.
Частые ошибки
Использовать Executors.newCachedThreadPool без верхнего лимита — может создать тысячи потоков.
Забыть shutdown() — JVM не остановится.
Дополнительные вопросы
Чем submit отличается от execute?
Что такое Virtual Threads?
Что такое Spring Bean и Dependency Injection?
Middle
Bean — это объект, управляемый Spring контейнером. Spring создаёт, инициализирует, инжектит зависимости и управляет жизненным циклом.
DI — внедрение зависимостей через конструктор, поле или setter. Это позволяет писать слабо связанный код и легко подменять реализации в тестах.
Из практики: предпочтительно injection через конструктор — явные зависимости, легко тестировать. Поле/setter — для специальных случаев.
Частые ошибки
Использовать field injection везде — сложно тестировать вне Spring.
Создавать слишком много beans без оправдания.
Дополнительные вопросы
Чем @Component отличается от @Service?
Что такое scope бина?
Что такое Maven и Gradle и когда что выбирать?
Middle
Maven — XML-конфигурация, conventions-driven, более предсказуемый. Gradle — DSL на Groovy/Kotlin, гибче, быстрее на инкрементальной сборке.
У обоих своя экосистема. Maven дольше существует, поэтому больше документации и плагинов.
Из практики: для серверных Java-проектов часто Maven (стабильность и понятность). Для Android и сложных мультимодульных — Gradle.
Частые ошибки
Смешивать Maven и Gradle в одном проекте без понимания.
Не использовать dependency-locking.
Дополнительные вопросы
Что такое dependency convergence?
Чем Gradle wrapper полезен?
Что такое аннотации и где их используют?
Middle
Аннотации — метаданные на классе, поле, методе. Бывают source (для компилятора), runtime (доступны через reflection).
Из практики: своих аннотаций обычно не делают — пользуются готовыми. Если делать — нужно понимание Retention, Target и reflection.
Частые ошибки
Использовать сложную аннотацию-магию без понимания.
Не учитывать, что reflection медленнее обычного кода.
Дополнительные вопросы
Что такое @Retention(RetentionPolicy.RUNTIME)?
Как написать свою аннотацию?
Что такое serialization и какие подходы есть?
Middle
Стандартная Java-сериализация (Serializable) — встроенная, но медленная и небезопасная (риск выполнения произвольного кода при десериализации).
Альтернативы: JSON (Jackson, Gson), Protocol Buffers, MessagePack. JSON — самый ходовой для HTTP API.
Из практики: для нового кода — JSON через Jackson. Serializable оставляют для специфичных случаев (RMI); из JDK его не удаляют, но считают ошибкой дизайна — поэтому есть фильтры десериализации (JEP 290) и долгосрочные планы заменить механизм.
Частые ошибки
Использовать ObjectInputStream с недоверенными данными — RCE.
Полагаться на Serializable для долговременного хранения.
Дополнительные вопросы
Что такое serialVersionUID?
Чем Jackson быстрее Gson?
Что такое pattern matching в современной Java?
Middle
С Java 17+ доступен pattern matching для instanceof: if (obj instanceof String s) { use s } — приведение и присваивание в одном.
Pattern matching for switch (Java 21) — мощнее: switch по типам, deconstruction record, охрана условиями.
Из практики: радикально снижает шаблонный код. Особенно ценно при работе с sealed классами и record.
Частые ошибки
Использовать старый instanceof + cast в новом коде.
Не учитывать exhaustiveness — switch требует покрытия всех вариантов sealed.
Дополнительные вопросы
Что такое record deconstruction?
Чем switch expression удобнее обычного?
Что такое Virtual Threads (Project Loom)?
Middle
Виртуальные потоки (Java 21) — лёгкие потоки, не привязанные 1:1 к OS-потокам. Можно запустить миллион виртуальных потоков, JVM мапит их на маленький пул carriers.
Хорошо подходят для IO-bound: блокирующий вызов внутри Virtual Thread не блокирует carrier — он перехватывает.
Из практики: для серверов, обрабатывающих тысячи соединений, virtual threads — революция. Не нужна асинхронность через CompletableFuture, можно писать обычный синхронный код.
Частые ошибки
Использовать virtual threads для CPU-bound — не помогут, нужно ограничение количества.
Pinning на synchronized — virtual thread «зависает» с carrier.
Дополнительные вопросы
Что такое pinning?
Чем virtual threads отличаются от Kotlin coroutines?
Что такое Selenide.open и как настроить базовый URL?
Junior
Selenide.open("/login") открывает страницу. baseUrl задаётся в Configuration.baseUrl или через System.property selenide.baseUrl.
Можно открыть конкретный класс page object: open("/login", LoginPage.class) — возвращает инициализированный объект.
Из практики: для разных окружений (dev/stage) baseUrl выносят в System.property, что позволяет переключаться без сборки.
Частые ошибки
Хардкодить полный URL в коде.
Не очищать состояние между тестами — cookies сохраняются.
Дополнительные вопросы
Как переключиться на другую среду в CI?
Что такое Selenide.clearBrowserCookies?
Чем sleep() и Selenide.sleep() отличаются от обычного Thread.sleep?
Middle
Selenide.sleep — обёртка над Thread.sleep с понятным API. Но любая sleep — анти-паттерн в тестах, потому что зависит от среды.
Лучше: используйте should/shouldBe с auto-wait. Если действительно нужно ждать — ждите по условию через Selenide.Wait().until.
Из практики: каждое использование sleep в тестах — потенциальный flaky. Перед merge-ом всё sleep пересматривают.
Частые ошибки
Использовать sleep везде «на всякий случай».
Поднимать timeout вместо причины замедления.
Дополнительные вопросы
Что такое Selenide.Wait()?
Чем should отличается от sleep + assertions?
Что такое CollectionCondition и как его использовать?
Middle
$$ возвращает ElementsCollection. Для него ассерты — CollectionCondition: size(N), texts("a", "b"), containExactTextsCaseSensitive, etc.
Пример: $$("li.row").shouldHave(size(5)). Auto-wait такой же, как у одиночных.
Из практики: незаменимо для проверки списков, таблиц, повторяющихся компонентов.
Частые ошибки
Использовать .size() и assertEquals — теряется retry.
Перебирать $$ в цикле с index и забыть про auto-wait.
Дополнительные вопросы
Что такое filter() для $$?
Как проверить, что в списке есть конкретный элемент?
Что такое selectOptionByText vs selectOptionByValue?
Junior
Для <select> Selenide предоставляет: selectOption(int index), selectOptionByValue(String), selectOptionContainingText(String), selectOption(String text).
Выбор зависит от стабильности: value стабильнее текста (особенно при локализации).
Из практики: для production-тестов предпочтительно selectOptionByValue. Если переводы стабильны и тестируется один язык — можно по тексту.
Частые ошибки
Зависеть от текста при тестировании нескольких локалей.
Использовать индекс — порядок может измениться.
Дополнительные вопросы
Как тестировать кастомный dropdown (не native select)?
Что такое selectRadio?
Что такое executeJavaScript в Selenide?
Middle
Selenide.executeJavaScript("return arguments[0].textContent", element) — выполняет JS в браузере. Возвращает результат.
Применяют для: чтения свойств, недоступных через WebDriver API; кастомных манипуляций с DOM; ускорения подготовки данных.
Из практики: использовать осторожно — JS-манипуляции далеки от пользовательского сценария. Лучше через UI, если возможно.
Частые ошибки
Делать через JS то, что можно через обычные действия.
Не учитывать различия движков (Chrome vs Firefox).
Дополнительные вопросы
Чем executeAsyncJavaScript отличается?
Когда JS обязателен?
Что такое addBaseUrl и navigator?
Middle
В публичном API Selenide методов addBaseUrl и navigator нет. Базовый URL задаётся через Configuration.baseUrl (или системным свойством/переменной окружения).
Selenide.open(path) открывает Configuration.baseUrl + path; если нужен абсолютный адрес — передают полный URL.
Навигация — статические методы Selenide.back(), forward(), refresh(); внутренний класс Navigator публично не используется.
Из практики: обычно достаточно open и переходов по UI; back/forward/refresh — для сценариев вроде Cancel в форме или восстановления после reload.
Частые ошибки
Использовать back() для отмены формы — лучше через UI.
Refresh посреди теста — теряется состояние.
Дополнительные вопросы
Как тестировать поведение при refresh?
Что такое Selenide.switchTo()?
Что такое browser webdriver listeners в Selenide?
Middle
SelenideLogger.addListener подключает свои обработчики событий теста: до/после действия. Так работает Allure listener — он перехватывает действия Selenide и формирует шаги.
Также можно сделать кастомные listener для метрик, отчётов, скриншотов на каждом шаге.
Из практики: Allure listener — стандарт. Подключают в @BeforeAll или один раз на проект.
Частые ошибки
Не подключать Allure и не получить шаги в отчёте.
Делать тяжёлые операции в listener — замедляются все тесты.
Дополнительные вопросы
Что такое EventListener в Selenium?
Чем AllureSelenide удобен?
Что такое downloadFolder в Selenide?
Middle
Configuration.downloadsFolder — путь для скачиваемых файлов. При FOLDER mode Selenide следит за этой папкой.
Также можно ввести Configuration.fileDownload = FOLDER / PROXY / HTTPGET — режимы скачивания.
Из практики: FOLDER подходит, когда нет сложной авторизации и достаточно мониторить папку. PROXY — для проксирования всех запросов и перехвата загрузок.
Частые ошибки
Не очищать downloadsFolder перед тестом — старые файлы могут спутать тест.
Использовать FOLDER в headless-Chrome без специальной настройки CDP.
Дополнительные вопросы
Что такое PROXY mode?
Чем CDP-настройка downloads?
Чем Selenide отличается от Selenium FluentWait?
Middle
В чистом Selenium для ожидания нужно явно создавать WebDriverWait и условие. В Selenide auto-wait встроен в каждое действие и каждый shouldBe.
Это устраняет 90% boilerplate WebDriverWait и делает тесты короче.
Из практики: можно по прежнему использовать FluentWait, если auto-wait недостаточно гибок (например, для нетривиальных условий).
Частые ошибки
Смешивать Selenide auto-wait и явный WebDriverWait — поведение усложняется.
Завышать Configuration.timeout вместо разбора причины.
Дополнительные вопросы
Когда auto-wait не подходит?
Что такое Selenide.Wait().until?
Какие подводные камни Selenide в headless-режиме?
Middle
В headless Chrome могут не работать: скачивание файлов (нужен CDP), некоторые анимации, особые pop-up. Screenshot работают.
Расширения, авторизация через сертификат, биометрия — не поддерживаются.
Из практики: для CI обычно достаточно headless. Для отладки сложных багов запускают в обычном режиме (Configuration.headless = false).
Частые ошибки
Запускать сложные сценарии (drag-drop с файлами) только в headless и удивляться, что в проде иначе.
Из практики: для REST API в Spring почти всегда @RestController. Возвращайте ResponseEntity для контроля над кодом и заголовками.
Частые ошибки
Возвращать сырые объекты без обёртки в DTO.
Не обрабатывать ошибки через @ControllerAdvice.
Дополнительные вопросы
Что такое @ControllerAdvice?
Чем @ResponseStatus полезен?
Что такое транзакционность @Transactional в Spring?
Middle
@Transactional оборачивает метод в БД-транзакцию. propagation определяет поведение при вложенных вызовах: REQUIRED (по умолчанию — переиспользовать или создать), REQUIRES_NEW (всегда новая), SUPPORTS, NEVER.
Откат: по умолчанию на unchecked exception. rollbackFor = Exception.class — откат на любых.
Из практики: типовая ошибка — вызывать @Transactional метод внутри того же класса — proxy не сработает, транзакция не создастся.
Частые ошибки
Вызывать @Transactional метод изнутри того же бина.
Можно подключаться к этапам через aware-интерфейсы и аннотации.
Из практики: для большинства задач хватает @PostConstruct для инициализации и @PreDestroy для очистки.
Частые ошибки
Делать тяжёлую работу в конструкторе.
Не закрывать ресурсы в @PreDestroy.
Дополнительные вопросы
Что такое scope бина?
Чем BeanPostProcessor полезен?
Что такое Mockito и как мокать зависимости?
Middle
Mockito — самая популярная библиотека для мокирования в Java. Создаёт фейковые объекты, чьё поведение можно настроить: when(mock.method()).thenReturn(value).
Из практики: моки помогают изолировать тестируемый класс от его зависимостей. Не моките value-объекты — это бесполезно.
Частые ошибки
Моки на final методы без mockito-inline.
Перемокать всё и тестировать моки, а не код.
Дополнительные вопросы
Что такое spy?
Чем @MockBean отличается от @Mock?
Что такое AssertJ и зачем?
Middle
Библиотека fluent assertions: assertThat(list).hasSize(3).contains("a").doesNotContain("x"). Цепочка проверок, читаемый код, понятные сообщения при ошибке.
Заменяет JUnit assertEquals/assertTrue для более выразительных проверок.
Из практики: для нового кода обычно подключают AssertJ. assertJ-core достаточен для большинства проверок.
Частые ошибки
Смешивать AssertJ и Hamcrest — путаница.
Использовать сырой assertEquals для сложных объектов.
Дополнительные вопросы
Чем AssertJ лучше Hamcrest?
Что такое soft assertions в AssertJ?
Что такое Testcontainers?
Middle
Библиотека для запуска Docker-контейнеров из Java-тестов. Запускает БД, брокеры, кеши — реальные, не моки.
Каждый тест может получить чистую инстанцию. После теста контейнер удаляется.
Из практики: радикально улучшает качество интеграционных тестов. Минус — медленнее юнит-тестов, поэтому отдельный профиль.
Частые ошибки
Использовать testcontainers в unit-тестах — медленно.
Не закрывать контейнеры — утечка.
Дополнительные вопросы
Что такое Singleton container pattern?
Чем testcontainers лучше моков?
Что такое RestAssured?
Middle
DSL для тестирования REST API в Java. given().contentType(JSON).body(req).when().post("/users").then().statusCode(201).body("id", notNullValue()).
Удобный синтаксис для send-validate. Поддерживает JSON Schema validation, OAuth, авторизацию.
Из практики: для функциональных тестов API в Java — стандарт. Альтернатива — HttpClient + ObjectMapper, но многословнее.
Частые ошибки
Использовать для производительности — RestAssured медленнее raw HTTP.
Полагать, что RestAssured заменит Postman.
Дополнительные вопросы
Чем JsonPath в RestAssured удобен?
Как тестировать контракты с RestAssured?
Что такое Java Records и Pattern Matching вместе?
Middle
record User(String name, int age) {} плюс switch (obj) { case User(String name, int age) -> ... } — компактные ADT в Java.
Pattern matching for switch (Java 21) даёт деструктуризацию record и exhaustive проверку для sealed.
Из практики: современный код выглядит почти как Kotlin/Scala. Намного выразительнее старых патентов.
Частые ошибки
Использовать record для бизнес-сущности с identity.
Не помнить, что record не может наследоваться.
Дополнительные вопросы
Чем record отличается от data class?
Что такое sealed + record + pattern matching ADT?
Что такое Optional vs null и когда что?
Middle
Optional<T> — обёртка, явно выражающая «значение может отсутствовать». Используется как возвращаемый тип методов.
Не для полей, не для параметров. Внутри полей и в простых случаях — допустимы null.
Из практики: Optional делает API безопаснее, но не превращайте всё в Optional — это создаёт лишний шум.