Тестирование в Java: Unit, Integration, E2E на продвинутом уровне

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

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

Начать курс

Тестирование в Java: Unit, Integration, E2E — Продвинутый уровень

В современной Java-разработке тестирование — это не просто формальность, а фундаментальная практика, обеспечивающая стабильность и предсказуемость кода. Базовые тесты, такие как простые @Test с Assert.assertEquals, давно уступили место сложным многослойным стратегиям. В этой статье мы разберем продвинутые техники для каждого уровня пирамиды тестирования: Unit, Integration и End-to-End (E2E). Вы узнаете, как писать поддерживаемые, быстрые и надежные тесты, которые превратят ваш код в хорошо отлаженный механизм.

1. Продвинутые Unit-тесты: Моки, Стабы и Параметризация

Unit-тесты проверяют изолированный кусок кода (обычно один класс или метод). На продвинутом уровне мы уходим от простых сценариев к сложным взаимодействиям и граничным случаям. Ключевой инструмент здесь — библиотека Mockito.

1.1. Использование ArgumentCaptor и сложных ответов (Answer)

Часто необходимо не просто проверить, что метод был вызван, но и какие аргументы ему были переданы. Для этого используется ArgumentCaptor.

// Пример: Проверка, что в сервис отправлено правильное сообщение
@ExtendWith(MockitoExtension.class)
public class NotificationServiceTest {
    @Mock
    private EmailSender emailSender;
    @InjectMocks
    private NotificationService notificationService;

    @Test
    void shouldSendEmailWithCorrectContent() {
        // Arrange
        User user = new User("test@example.com", "Test User");
        // Act
        notificationService.sendWelcomeEmail(user);
        // Assert
        ArgumentCaptor<EmailMessage> captor = ArgumentCaptor.forClass(EmailMessage.class);
        verify(emailSender).send(captor.capture());
        EmailMessage capturedMessage = captor.getValue();
        assertEquals("test@example.com", capturedMessage.getTo());
        assertTrue(capturedMessage.getBody().contains("Test User"));
    }
}

Для динамического создания ответов на основе входных данных используйте интерфейс Answer:

@Test
void testDynamicResponse() {
    when(dataRepository.findById(anyLong())).thenAnswer(invocation -> {
        Long id = invocation.getArgument(0);
        return new DataEntity(id, "Entity with ID: " + id);
    });
    assertEquals("Entity with ID: 5", dataService.getDataById(5L).getName());
}

1.2. Параметризованные тесты (JUnit 5)

Параметризация позволяет запускать один и тот же тест с разными наборами данных, избегая дублирования кода. JUnit 5 предоставляет мощные аннотации, такие как @CsvSource и @MethodSource.

@ParameterizedTest
@CsvSource({
    "10, 20, 30",
    "0, 0, 0",
    "-5, 5, 0"
})
void testSum(int a, int b, int expectedSum) {
    Calculator calculator = new Calculator();
    assertEquals(expectedSum, calculator.add(a, b));
}

2. Интеграционное тестирование: Работа с базой данных и внешними сервисами

Интеграционные тесты проверяют взаимодействие между компонентами системы, например, с реальной базой данных или очередями. Главный вызов — управление состоянием окружения. Здесь на помощь приходит Testcontainers.

2.1. Testcontainers для изолированной БД

Testcontainers поднимает Docker-контейнеры с необходимым ПО (PostgreSQL, Redis, Kafka) прямо во время тестов, гарантируя чистоту и воспроизводимость.

@SpringBootTest
@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 shouldSaveAndFindUser() {
        User user = new User("john.doe@example.com", "John Doe");
        userRepository.save(user);
        Optional<User> foundUser = userRepository.findByEmail("john.doe@example.com");
        assertTrue(foundUser.isPresent());
        assertEquals("John Doe", foundUser.get().getName());
    }
}

2.2. Тестирование REST API с MockMvc и WebTestClient

Для тестирования контроллеров без поднятия полного сервера используйте MockMvc (Spring MVC) или WebTestClient (WebFlux).

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnUserById() throws Exception {
        mockMvc.perform(get("/api/users/1")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("John Doe"));
    }
}

3. End-to-End (E2E) тестирование: Сквозные сценарии

E2E-тесты проверяют систему целиком, имитируя действия реального пользователя через UI. Java-экосистема предлагает мощные инструменты, такие как Selenium WebDriver и Playwright.

3.1. Структура E2E теста с Selenium и Page Object Model

Паттерн Page Object Model (POM) — это золотой стандарт для E2E-тестов. Он абстрагирует детали HTML-страницы, делая тесты читаемыми и устойчивыми к изменениям верстки.

// Page Object для страницы логина
public class LoginPage {
    private WebDriver driver;
    @FindBy(id = "username")
    private WebElement usernameInput;
    @FindBy(id = "password")
    private WebElement passwordInput;
    @FindBy(id = "login-button")
    private WebElement loginButton;

    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public void login(String username, String password) {
        usernameInput.sendKeys(username);
        passwordInput.sendKeys(password);
        loginButton.click();
    }
}

// Сам тест
public class LoginE2ETest {
    private WebDriver driver;
    private LoginPage loginPage;

    @BeforeEach
    void setUp() {
        driver = new ChromeDriver();
        driver.get("https://example.com/login");
        loginPage = new LoginPage(driver);
    }

    @Test
    void testSuccessfulLogin() {
        loginPage.login("admin", "                

Блоги

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