THREADQA
    THREADQA
    Главная
    Курсы
    Java QA AutomationNEW
    Новый курс · уже можно купить
    Python QA Automation
    Pytest, Playwright, Docker
    iOS QA Automation
    XCTest, XCUITest, Fastlane
    Все курсы
    Практика
    Мок собеседование
    Тренировка перед реальным интервью
    Записи собеседований
    Разбор реальных собеседований
    Буткемп
    Интенсивная подготовка к работе
    XPath Practice Hub
    Тренажёр XPath-запросов
    Roadmap
    Путь QA-инженера
    XPath Dinner
    Практика XPath в игровом формате
    Блог
    FAQ
    Для компаний
    1. Домой
    2. Обучение
    3. Архитектура проекта автотестов на Java: Builder, Factory, fixtures и слои
    Все статьи
    Обучение
    10 августа 2026 г. 14 мин чтения

    Архитектура проекта автотестов на Java: Builder, Factory, fixtures и слои

    Практический разбор структуры Java-проекта для QA Automation: как разделить tests, clients, pages, models и fixtures, где применять Builder и Factory и как не превратить фреймворк автотестов в набор Utils.

    Олег Пендрак
    Олег Пендрак
    Tech Lead QA Automation · Ozon, VK

    С чего начинается нормальная архитектура автотестов

    Хороший проект автотестов начинается не с BaseTest и не с папки utils. Он начинается с границ ответственности. Тест должен описывать бизнес-сценарий, API-клиент — общение с сервисом, Page Object — действия с интерфейсом, fixture — подготовку и очистку данных, а model — данные без лишней логики.

    Если один тест одновременно собирает JSON, вызывает REST Assured, пишет SQL, ищет элементы Selenide и вручную удаляет созданные данные — проблема не в длине теста. Проблема в том, что инфраструктурные детали протекли в сценарий.

    Базовая структура Java-проекта автотестов

    Для большинства проектов удобнее группировать код по ответственности, а не складывать всё рядом с тестами. Ниже — пример структуры, от которой можно отталкиваться. Это не догма: названия пакетов можно менять под домен команды.

    text
    1src/test/java
    2├── tests
    3│   ├── api
    4│   ├── ui
    5│   └── integration
    6├── clients
    7│   ├── OrderApiClient.java
    8│   └── UserApiClient.java
    9├── pages
    10│   ├── LoginPage.java
    11│   └── OrdersPage.java
    12├── models
    13│   ├── Order.java
    14│   └── User.java
    15├── factories
    16│   ├── OrderFactory.java
    17│   └── UserFactory.java
    18├── fixtures
    19│   ├── OrderFixture.java
    20│   └── UserFixture.java
    21├── repositories
    22│   └── OrderRepository.java
    23├── assertions
    24│   └── OrderAssertions.java
    25├── config
    26│   └── TestConfig.java
    27└── support
    28    ├── ApiSpec.java
    29    └── TestEnvironment.java

    Самая важная идея: tests должны зависеть от более низких слоёв, но низкие слои не должны знать о конкретных тестах. OrderApiClient не должен содержать метод createOrderForShouldReturn400Test(). Клиент знает, как вызвать endpoint. Сценарий знает, зачем он это делает.

    Каким должен быть сам тест

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

    java
    1@Test
    2void customerCanCreateOrder() {
    3    var customer = userFixture.createCustomer();
    4    var order = OrderFactory.validOrderFor(customer.id());
    5
    6    var createdOrder = orderApi.create(order);
    7
    8    assertThat(createdOrder.status()).isEqualTo("CREATED");
    9    orderAssertions.assertStoredInDatabase(createdOrder.id());
    10}

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

    Builder: когда он действительно полезен

    Builder полезен для сложных тестовых данных, где у объекта много необязательных полей и тесту нужно менять только 1–2 параметра. Особенно хорошо он работает для request DTO, событий Kafka и доменных моделей.

    java
    1var order = OrderRequest.builder()
    2    .customerId(customer.id())
    3    .restaurantId(42L)
    4    .deliveryType(DeliveryType.COURIER)
    5    .comment("leave at the door")
    6    .build();

    Но Builder сам по себе не решает проблему тестовых данных. Если каждый тест вручную заполняет десять обязательных полей, код всё равно будет шумным. Поэтому Builder обычно удобнее сочетать с Factory.

    Factory: готовые валидные объекты вместо копипаста

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

    java
    1public final class OrderFactory {
    2
    3    private OrderFactory() {}
    4
    5    public static OrderRequest validOrderFor(long customerId) {
    6        return OrderRequest.builder()
    7            .customerId(customerId)
    8            .restaurantId(42L)
    9            .deliveryType(DeliveryType.COURIER)
    10            .items(List.of(new OrderItem(101L, 2)))
    11            .build();
    12    }
    13}

    В тесте после этого меняется только то, что важно конкретному сценарию. Например, для негативного кейса можно взять валидный объект и изменить одно поле. В Java для этого часто удобно использовать toBuilder() у Lombok или явный метод copy.

    java
    1var invalidOrder = OrderFactory.validOrderFor(customer.id())
    2    .toBuilder()
    3    .restaurantId(null)
    4    .build();

    Builder и Factory — не конкуренты

    ИнструментЧто решаетКогда применять
    BuilderУдобная пошаговая сборка объектаМного полей, много комбинаций, нужны точечные изменения
    FactoryСоздание готового осмысленного варианта данныхНужны валидные дефолты и повторяемые сценарии
    FixtureСоздание данных в реальной системе и cleanupНужно вызвать API/БД и удалить данные после теста

    Распространённая ошибка — называть Factory любой класс, который просто вызывает new. Хорошая Factory добавляет смысл: validCustomer(), premiumUser(), orderWithTwoItems(). Она кодирует допустимые варианты тестовых данных и делает тесты короче.

    Fixture: где заканчивается Factory и начинается инфраструктура

    Factory создаёт объект в памяти. Fixture создаёт состояние в тестируемой системе. Например, UserFactory может вернуть UserRequest, а UserFixture — вызвать API, получить id и зарегистрировать удаление созданного пользователя после теста.

    java
    1public class UserFixture {
    2    private final UserApiClient userApi;
    3    private final Deque<Long> createdUsers = new ArrayDeque<>();
    4
    5    public UserFixture(UserApiClient userApi) {
    6        this.userApi = userApi;
    7    }
    8
    9    public User createCustomer() {
    10        var request = UserFactory.validCustomer();
    11        var user = userApi.create(request);
    12        createdUsers.push(user.id());
    13        return user;
    14    }
    15
    16    public void cleanup() {
    17        while (!createdUsers.isEmpty()) {
    18            userApi.delete(createdUsers.pop());
    19        }
    20    }
    21}

    Так тест не обязан помнить, какие id нужно удалить. Cleanup становится частью жизненного цикла fixture. В реальном проекте это можно связать с JUnit 5 Extension, @AfterEach или отдельным FixtureScope.

    API client: не превращайте его в тест

    API client должен инкапсулировать HTTP-детали: URL, headers, сериализацию, спецификации REST Assured и разбор ответа. Но бизнес-проверки лучше не прятать внутрь клиента.

    java
    1public Order create(OrderRequest request) {
    2    return given()
    3        .spec(apiSpec)
    4        .body(request)
    5    .when()
    6        .post("/api/orders")
    7    .then()
    8        .statusCode(201)
    9        .extract().as(Order.class);
    10}

    Если тесту нужно проверить 400, клиенту лучше дать низкоуровневый вариант метода, который возвращает Response, или отдельный метод без жёсткой проверки status code. Не стоит создавать десятки методов вроде createOrderExpecting400(), createOrderExpecting409() только ради разных assert.

    Page Object: хранит поведение страницы, а не весь тест

    Для UI действует тот же принцип. Page Object скрывает локаторы и простые действия, но не должен превращаться в огромный класс со всеми бизнес-сценариями приложения.

    java
    1public class LoginPage {
    2    private final SelenideElement email = $("[data-testid=email]");
    3    private final SelenideElement password = $("[data-testid=password]");
    4    private final SelenideElement submit = $("[data-testid=login]");
    5
    6    public void login(String userEmail, String userPassword) {
    7        email.setValue(userEmail);
    8        password.setValue(userPassword);
    9        submit.click();
    10    }
    11}

    Метод login() здесь уместен: это законченное действие на странице. А вот createCustomerAndPlaceOrderThroughUiAndCheckDatabase() — уже оркестрация нескольких слоёв, ей место в тесте или отдельном domain/service layer.

    Нужен ли BaseTest

    BaseTest не является обязательным паттерном. Маленький базовый класс с общим lifecycle допустим, но глубокая иерархия BaseApiTest → AuthenticatedApiTest → OrderBaseTest быстро создаёт скрытые зависимости. Через несколько месяцев разработчик уже не понимает, откуда взялась конфигурация, пользователь или cleanup.

    • ▸Используйте composition чаще, чем inheritance.
    • ▸Общие зависимости передавайте через fixture, extension или объект окружения.
    • ▸Не храните изменяемое состояние тестов в static-полях.
    • ▸Не создавайте BaseTest только потому, что «так принято».

    Почему папка Utils часто становится проблемой

    Utils обычно начинается с пары методов, а заканчивается DateUtils, JsonUtils, ApiUtils, TestUtils и сотнями несвязанных функций. Это признак потерянной ответственности. Метод генерации пользователя лучше держать в UserFactory, чтение конфигурации — в config, работу с JSON — рядом с сериализацией, а ожидание Kafka-события — в KafkaTestBus или специализированном helper.

    Полезный вопрос при добавлении метода: к какому объекту предметной области или инфраструктуры он относится? Если ответ есть, скорее всего отдельный Utils ему не нужен.

    Когда нужен отдельный service/domain layer

    Если один и тот же бизнес-сценарий повторяется во многих тестах и включает несколько клиентов, можно добавить сервисный слой. Например, OrderSteps или OrderScenario может создать пользователя, оформить заказ и дождаться Kafka-события. Но такой слой стоит вводить только после появления реального повторения.

    Не нужно заранее строить десять уровней абстракции. Хорошая архитектура тестов растёт вместе с проектом: сначала простой client + test, затем factory/fixture, а service layer появляется только там, где он уменьшает дублирование и делает сценарии понятнее.

    Антипаттерны, которые чаще всего ломают тестовый проект

    • ▸Огромный BaseTest, который создаёт половину инфраструктуры до каждого теста.
    • ▸Статические mutable-поля с текущим пользователем, токеном или orderId.
    • ▸Один ApiHelper на все endpoints приложения.
    • ▸Page Object на 1500 строк с бизнес-логикой и assert внутри каждого метода.
    • ▸Factory без дефолтов, где тест всё равно заполняет каждое поле вручную.
    • ▸Cleanup через sleep или ручное удаление в конце теста: если assert упадёт раньше, данные останутся.
    • ▸Наследование ради переиспользования двух методов.
    • ▸Абстракции «на будущее», которыми пока пользуется только один тест.

    Чек-лист архитектуры Java QA Automation проекта

    • ▸Тест читается как сценарий, а не как набор HTTP/SQL/UI-команд.
    • ▸API clients отвечают за транспорт и сериализацию.
    • ▸Page Objects скрывают локаторы и действия конкретной страницы.
    • ▸Models/DTO не зависят от тестов.
    • ▸Factory создаёт осмысленные варианты тестовых данных.
    • ▸Builder помогает точечно менять сложные объекты.
    • ▸Fixtures управляют созданием и cleanup данных.
    • ▸Проверки, которые повторяются, вынесены в assertions/domain helpers.
    • ▸Нет глобального изменяемого состояния между тестами.
    • ▸Параллельный запуск не ломает данные и окружение.
    • ▸Новая абстракция появляется из реальной боли, а не из желания применить паттерн.

    Как понять, что архитектура стала лучше

    Главный критерий — не количество паттернов. Хорошая архитектура уменьшает стоимость изменения тестов. Если endpoint поменял URL, вы исправляете client, а не 70 тестов. Если изменился локатор, правите Page Object. Если появились новые обязательные поля заказа, обновляете Factory. Если тест упал, по его коду понятно, какой бизнес-сценарий сломан.

    Builder, Factory, Page Object и fixtures — это не цель. Это инструменты, которые помогают отделить сценарий от инфраструктуры. Используйте их тогда, когда они делают код тестов короче, понятнее и безопаснее для изменений.

    Куда двигаться дальше

    Если хочется сначала увидеть полный список навыков Java QA Automation, открой бесплатный roadmap. А если нужен законченный пример проекта с API, UI, PostgreSQL, Kafka, fixtures и CI/CD, посмотри программу Java QA Automation ThreadQA.

    Открыть Java QA Automation Roadmap
    Посмотреть программу Java QA Automation
    #архитектура автотестов java#структура проекта qa automation#builder pattern java tests#factory pattern java tests#fixtures java qa#java qa automation#test automation architecture#selenide rest assured architecture#паттерны проектирования автотесты

    Хочешь практиковаться, а не только читать?

    Курсы по Java, Python и iOS автоматизации. Первые уроки бесплатно.

    Начать бесплатно

    Читайте также

    Обучение
    18 мин

    Java QA Automation Engineer с нуля в 2026: полный путь от ручного тестировщика до оффера

    Большой гайд по Java QA Automation в 2026 году: что учить с нуля, какой стек нужен Junior и Middle, сколько времени занимает обучение, как собрать портфолио, подготовиться к собеседованию и когда стоит идти на курс.

    Общие темы:java qa automation
    Обучение
    16 мин

    Selenide для начинающих: полный гайд по автоматизации тестирования в 2026

    Изучаем Selenide с нуля: установка, первые тесты, Page Object Model, Container, кастомные проверки, работа с элементами. Практический туториал для Java QA Automation.

    Общие темы:java qa automation
    Обучение
    7 мин

    Java или Python для QA Automation: честное сравнение в 2026

    Сравниваем Java и Python для автоматизации тестирования: вакансии, зарплаты, сложность обучения, инструменты. Что выбрать новичку в QA Automation в 2026 году.

    Общие темы:java qa automation
    Все статьи блога

    Содержание

    С чего начинается нормальная архитектура автотестовБазовая структура Java-проекта автотестовКаким должен быть сам тестBuilder: когда он действительно полезенFactory: готовые валидные объекты вместо копипастаBuilder и Factory — не конкурентыFixture: где заканчивается Factory и начинается инфраструктураAPI client: не превращайте его в тестPage Object: хранит поведение страницы, а не весь тестНужен ли BaseTestПочему папка Utils часто становится проблемойКогда нужен отдельный service/domain layerАнтипаттерны, которые чаще всего ломают тестовый проектЧек-лист архитектуры Java QA Automation проектаКак понять, что архитектура стала лучшеКуда двигаться дальше

    Автор

    Олег Пендрак
    Олег Пендрак
    Tech Lead QA

    Опыт в Ozon и VK. YouTube-канал 10к+ подписчиков.

    Готов к практике?

    Первые уроки бесплатно

    Начать бесплатно
    THREADQAПлатформа QA Automation

    О платформе

    Обучаем автоматизации тестирования на Java, Python и iOS. Курсы, мок-интервью, буткемп с менторством до оффера.

    Онлайн 24/7

    Курсы

    • Java QA AutomationNEW
    • Python QA Automation
    • iOS QA Automation
    • Про ThreadQA

    Услуги

    • QA Буткемп
    • Мок-собеседования
    • Записи собеседований

    Инструменты

    • Roadmap QA
    • Тренажёр XPath
    • XPath Diner

    Контакты

    • Email
      info@threadqa.ru
    • Telegram
      @penolegrus
    Публичная офертаПолитика конфиденциальностиУсловия использования
    © 2026·ThreadQA LMS·Все права защищены