Тестирование в Java: unit, integration, e2e от А до Я

Онлайн-тренажер Python для начинающих

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

Начать курс

Введение: Почему тестирование — фундамент качественного Java-кода

Каждый разработчик Java сталкивался с ситуацией, когда после исправления одного бага появлялось три новых. Чтобы разорвать этот порочный круг, существует тестирование. Это не просто галочка в чек-листе, а профессиональная дисциплина, которая спасает нервы, деньги и репутацию продукта.

В этой статье мы разберем три ключевых уровня тестирования: unit (модульное), integration (интеграционное) и e2e (end-to-end). Мы рассмотрим их на практических примерах Java, используя популярные фреймворки: JUnit 5, Mockito, Spring Boot Test и Selenium. Вы узнаете, как построить правильную пирамиду тестирования и не перегрузить проект бесполезными тестами.

1. Unit-тестирование: Проверка атомарных единиц кода

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

1.1 Инструменты и фреймворки

  • JUnit 5 — стандарт де-факто для написания тестов в Java.
  • Mockito — библиотека для создания заглушек (mocks) и стабов (stubs).
  • AssertJ — библиотека для fluent-проверок (утверждений).

1.2 Пример Unit-теста с JUnit 5 и Mockito

Представим, что у нас есть сервис OrderService, который зависит от репозитория PaymentRepository. Мы хотим протестировать логику сервиса, не вызывая реальную БД.

// Тестируемый класс
public class OrderService {
    private final PaymentRepository paymentRepository;

    public OrderService(PaymentRepository paymentRepository) {
        this.paymentRepository = paymentRepository;
    }

    public String processOrder(Order order) {
        if (order.getAmount() <= 0) {
            throw new IllegalArgumentException("Сумма заказа должна быть положительной");
        }
        boolean success = paymentRepository.pay(order);
        return success ? "Успешно оплачено" : "Ошибка оплаты";
    }
}

// Unit-тест
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private PaymentRepository paymentRepository;

    @InjectMocks
    private OrderService orderService;

    @Test
    void testProcessOrder_Success() {
        // Arrange
        Order order = new Order(100.0);
        when(paymentRepository.pay(order)).thenReturn(true);

        // Act
        String result = orderService.processOrder(order);

        // Assert
        assertEquals("Успешно оплачено", result);
        verify(paymentRepository, times(1)).pay(order);
    }

    @Test
    void testProcessOrder_NegativeAmount() {
        // Arrange
        Order order = new Order(-10.0);

        // Act & Assert
        assertThrows(IllegalArgumentException.class, () -> {
            orderService.processOrder(order);
        });
    }
}

Best Practices для Unit-тестов:

  • Один тест — одна проверка (принцип FIRST: Fast, Isolated, Repeatable, Self-validating, Timely).
  • Используйте понятные имена: shouldReturnSuccess_whenPaymentIsApproved.
  • Не тестируйте private-методы — тестируйте публичный контракт.
  • Покрывайте граничные случаи: null, пустые строки, отрицательные числа.

2. Интеграционное тестирование: Проверка взаимодействия компонентов

Интеграционные тесты проверяют, как разные модули системы работают вместе: с реальной базой данных, очередями сообщений или внешними REST-сервисами. Они медленнее unit-тестов, но дают больше уверенности.

2.1 Инструменты для интеграционного тестирования Java

  • Spring Boot Test — аннотации @SpringBootTest, @DataJpaTest.
  • Testcontainers — запуск Docker-контейнеров (PostgreSQL, Redis) в тестах.
  • H2 (in-memory DB) — легковесная БД для быстрых тестов (не подходит для production-запросов).

2.2 Пример интеграционного теста с Spring Boot и Testcontainers

Проверим, что наш репозиторий корректно сохраняет и извлекает пользователя из реальной PostgreSQL.

// Сущность
@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    // getters/setters...
}

// Интеграционный тест
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@Testcontainers
class UserRepositoryIntegrationTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("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);
    }

    @Autowired
    private UserRepository userRepository;

    @Test
    void shouldSaveAndFindUserByEmail() {
        // Arrange
        User user = new User();
        user.setEmail("ivan@example.com");

        // Act
        User savedUser = userRepository.save(user);
        User foundUser = userRepository.findByEmail("ivan@example.com").orElse(null);

        // Assert
        assertThat(foundUser).isNotNull();
        assertThat(foundUser.getId()).isGreaterThan(0);
        assertThat(foundUser.getEmail()).isEqualTo("ivan@example.com");
    }
}

Совет: Используйте Testcontainers вместо H2 для production-совместимости. H2 часто ведет себя иначе, чем PostgreSQL/MySQL.

3. E2E-тестирование: Сквозная проверка пользователь

Блоги

Рекомендаций по книгам