Лет пять назад я собеседовал кандидата после курсов. Резюме красивое: "яндекс практикум автоматизация тестирования на java", сертификат, проекты в портфолио. Спрашиваю: как вы запускаете тесты в CI? Тишина. А как тесты параллелить? Ещё тише.
Парень знал Selenium. Умел писать тесты. Но между "написать тест" и "тесты работают в продакшене" — пропасть. Вот про эту пропасть и поговорим. Потому что курсов куча, а вот как собрать это в работающую систему — информации мало.
Стек: что реально используется в индустрии
Если вы учитесь — скорее всего, вам дают JUnit, Selenium, может быть TestNG. Это база. Но в продакшене картинка другая.
JUnit 5 — стандарт де-факто. Если видите проект на JUnit 4 — это легаси, и его переписывают. JUnit 5 даёт параметризацию из коробки, нормальную модель расширений, наконец-то адекватные ассерты.
Selenide вместо чистого Selenium. Честно, я давно не видел проектов, где пишут на чистом Selenium. Selenide оборачивает его в человеческий API:
open("/login");
$(By.id("email")).setValue("user@test.com");
$(By.id("password")).setValue("password123");
$("button[type=submit]").click();
$(".welcome-message").shouldHave(text("Добро пожаловать"));
Не нужно думать про waits, не нужно ловить StaleElementReferenceException. Работает.
RestAssured для API-тестов. Если вы тестируете только UI — вы тестируете только UI. А бизнес-логика живёт в API. RestAssured даёт читаемые тесты:
given()
.header("Authorization", token)
.body(requestBody)
.when()
.post("/api/orders")
.then()
.statusCode(201)
.body("id", notNullValue())
.body("status", equalTo("CREATED"));
Да, в курсах типа "яндекс практикум автоматизация тестирования на java" или "яндекс практикум автоматизация тестирования python" это дают. Но часто — поверхностно. Фокус на написании тестов, а не на архитектуре тестового проекта.
CI/CD: где живут тесты
Тесты без CI — это просто файлы на чьём-то ноутбуке. Они не работают.
Базовая схема: код → коммит → автоматический запуск тестов → деплой. Звучит просто. Но есть нюансы.
Уровни тестирования в пайплайне
Юнит-тесты запускаются на каждый коммит. Быстро, параллельно. Если падают — билд красный, мердж запрещён.
Интеграционные тесты — на merge request. Дольше, но ещё терпимо. Используют тестовые среды, моки внешних сервисов.
E2E тесты — по расписанию или перед релизом. Медленные, хрупкие, но проверяют реальный user flow. Их не запустишь на каждый коммит — часами будут идти.
Пример GitLab CI для Java-проекта
stages:
- build
- test
- e2e
- deploy
variables:
MAVEN_OPTS: "-Dmaven.repo.local=.m2/repository"
cache:
paths:
- .m2/repository/
build:
stage: build
image: maven:3.8-openjdk-17
script:
- mvn compile
unit-tests:
stage: test
image: maven:3.8-openjdk-17
script:
- mvn test -Dtest="*UnitTest"
artifacts:
reports:
junit: target/surefire-reports/*.xml
integration-tests:
stage: test
image: maven:3.8-openjdk-17
services:
- postgres:14
- redis:7
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
script:
- mvn verify -Dtest="*IntegrationTest"
only:
- merge_requests
e2e-tests:
stage: e2e
image: maven:3.8-openjdk-17
services:
- selenium/standalone-chrome:latest
script:
- mvn verify -Dtest="*E2ETest" -Dselenium.host=selenium
only:
- main
when: manual
Это рабочая конфигурация. Не идеальная, но рабочая. Юнит-тесты гоняются всегда, интеграционные — только в MR, E2E — вручную на main.
GitHub Actions — альтернатива
name: Test Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven
- run: mvn test
integration-tests:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_DB: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
- run: mvn verify -Dtest="*IntegrationTest"
GitHub Actions удобнее для open-source. GitLab CI — для внутренних проектов. Но принцип один: тесты — часть пайплайна, не отдельная активность.
Code review тестов: что часто упускают
Тесты — это код. Их тоже нужно ревьюить. Но на практике — "тесты работают, ок, мерджим". И это проблема.
Типичные косяки, которые я вижу:
Хардкод данных. Логины, пароли, URL — всё захардкожено. Потом среду поменяли — тесты упали. Используйте конфигурацию:
public class Config {
private static final String BASE_URL = System.getProperty("baseUrl", "https://test.example.com");
private static final String TEST_USER = System.getProperty("testUser", "test@example.com");
public static String getBaseUrl() {
return BASE_URL;
}
}
Запускаете так: mvn test -DbaseUrl=https://staging.example.com
Отсутствие изоляции. Тест меняет состояние, следующий тест падает. Каждый тест должен быть независим. Before/After аннотации — не просто так придуманы.
Проверка реализации вместо поведения. Тест знает про внутренности тестируемого кода. Меняется имплементация — падает тест, хотя поведение то же. Это хрупкие тесты, они съедают время на поддержку.
Параллелизация: когда тестов становится много
На одном проекте у нас было 2000+ тестов. Последовательно шли 40 минут. В CI это неприемлемо.
Решение — параллельный запуск. JUnit 5 поддерживает из коробки:
@Configuration
public class ParallelTestConfig {
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.enabled", value = "true")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.mode.default", value = "concurrent")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.config.strategy", value = "dynamic")
@ConfigurationParameter(key = "junit.jupiter.execution.parallel.config.dynamic.factor", value = "2")
}
В Maven — Surefire plugin с настройками:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<parallel>methods</parallel>
<threadCount>4</threadCount>
<perCoreThreadCount>true</perCoreThreadCount>
</configuration>
</plugin>
С 40 минут до 12. Уже лучше.
Но есть нюанс. Если тесты используют общие ресурсы — базу, файлы — параллелизация сломается. Нужна изоляция. Container Testcontainers — ваш друг:
@Testcontainers
public class BaseIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:14")
.withDatabaseName("test");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
Каждый тестовый класс получает свою базу. Изоляция полная.
Отчёты и аналитика
Тесты падают. Это нормально. Ненормально — когда вы не понимаете, почему.
Allure — стандарт для отчётов. Интегрируется с JUnit, TestNG, почти со всем. Даёт историю, тренды, понятные репорты:
@Epic("User Management")
@Feature("Login")
public class LoginTest {
@Story("Successful login")
@Severity(SeverityLevel.BLOCKER)
@Test
public void userCanLoginWithValidCredentials() {
// test body
}
}
Отчёт покажет: какие фичи покрыты, где проблемы, какие тесты самые хрупкие. Хрупкие тесты — это отдельная боль. Если тест падает в 30% случаев без изменения кода — это плохой тест. Его нужно переписывать или удалять.
Flaky-тесты съедают доверие к тестированию. Команда привыкает: "упало, перезапустим". А там уже реальный баг. Но никто не смотрит.
Что в итоге
Автоматизация тестирования — это не про Selenium. Это про систему. Процесс. Инфраструктуру.
Курсы дают базу. "Яндекс практикум автоматизация тестирования на java" или аналоги — окей для старта. Но реальный опыт — это когда вы настроили CI, разобрались с параллелизацией, победили flaky-тесты, внедрили нормальные отчёты.
Ну и последнее. Code review тестов так же важен, как и code review продакшен-кода. Мы в команде используем Distiq — это AI-бот, который автоматически проверяет каждый MR. Он ловит типовые проблемы: захардкоженные креды, missing asserts, некорректную изоляцию. Не заменяет человека, но экономит время на базовые проверки. Ссылка: distiq.ru, если интересно.
