W3docs

Введение в тестирование Java

Зачем нужно тестирование в Java, пирамида тестирования и обзор популярных фреймворков для тестирования.

Автоматическое тестирование — это способ доказать, что код делает именно то, что вы ожидаете, и продолжать это доказывать по мере изменений кода. Вместо того чтобы запускать программу вручную и проверять вывод на глаз, вы пишете небольшие программы, которые выполняют ваш код и автоматически проверяют результаты. В Java эта экосистема строится вокруг фреймворков JUnit и Mockito, но основные идеи — расставить, выполнить, проверить — достаточно просты, чтобы реализовать их вручную. Эта глава описывает общую картину, прежде чем последующие главы подробно рассмотрят каждый инструмент.

Зачем нужно автоматическое тестирование

Тест — это небольшая, повторяемая проверка того, что фрагмент кода работает корректно. Ценность заключается не в первом запуске, а в каждом последующем. Как только поведение зафиксировано в тесте, любое изменение, которое его нарушает, немедленно и громко сигнализирует об ошибке — вместо того чтобы всплыть в виде бага в продакшене через несколько недель. Тесты также документируют намерения: хорошо названный тест говорит, что код должен делать.

// A test names a behavior, runs the code, and asserts the outcome.
@Test
void addsTwoPositiveNumbers() {
    int result = Calculator.add(2, 3);
    assertEquals(5, result);   // fails the build if result != 5
}

Цель — быстрая обратная связь. Зелёный набор тестов означает, что вы можете рефакторить с уверенностью; красный сразу указывает на то, что сломалось.

Пирамида тестирования

Тесты делятся на уровни, которые обычно изображают в виде пирамиды. Модульные тесты находятся в основании: их много, они быстрые, каждый проверяет один класс или метод в изоляции. Интеграционные тесты находятся в середине: их меньше, они медленнее, проверяют взаимодействие компонентов (ваш код вместе с базой данных, например). Сквозные (E2E) тесты находятся на вершине: их мало, они самые медленные, управляют всем приложением так, как это делал бы пользователь.

УровеньОбластьСкоростьКоличествоИнструменты Java
Модульныйодин класс/методбыстрая (мс)многоJUnit, AssertJ
Интеграционныйнесколько компонентовсредняянекоторыеJUnit, Testcontainers
Сквознойвся системамедленнаямалоSelenium, REST-assured

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

Паттерн «расставить–выполнить–проверить»

Практически каждый тест в любом фреймворке следует одной и той же трёхшаговой структуре. Расставьте входные данные и зависимости. Выполните код, который тестируется. Проверьте, что результат соответствует ожидаемому. Визуальное разделение этих шагов делает тест удобным для чтения и отладки при сбое.

@Test
void rejectsBlankUsername() {
    // Arrange
    UserService service = new UserService();

    // Act
    boolean valid = service.isValidUsername("   ");

    // Assert
    assertFalse(valid);
}

Неудачная проверка выбрасывает исключение, фреймворк фиксирует его, и выполнение продолжается на следующем тесте — таким образом, одно сломанное поведение никогда не скрывает остальные.

JUnit — стандартный раннер

JUnit — де-факто фреймворк для модульного тестирования в Java. Вы аннотируете методы с помощью @Test, JUnit обнаруживает их через рефлексию, запускает каждый и сообщает об успехе или неудаче. Проверки вроде assertEquals, assertTrue и assertThrows — это статические вспомогательные методы, которые провалят тест, если ожидание не выполнено. В реальных проектах JUnit запускается через инструмент сборки (плагин Surefire для Maven или задача test в Gradle), а не вручную.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {
    @Test
    void dividesNumbers() {
        assertEquals(4, Calculator.divide(8, 2));
    }

    @Test
    void throwsOnDivideByZero() {
        assertThrows(ArithmeticException.class, () -> Calculator.divide(1, 0));
    }
}

Поскольку JAR-файл JUnit и инструменты сборки недоступны в этой среде, пример ниже реализует ту же идею с нуля — минималистичный обвязчик, который запускает именованные проверки и подсчитывает успехи и неудачи, — именно то, что @Test вместе с assertEquals делают под капотом.

java— editable, runs on the server

Что стоит вынести из запуска:

  • Каждый вызов assertEquals — это один тест-кейс: расставляем входные данные, выполняем add или isBlank, проверяем результат — в точности то, что делает метод @Test в JUnit.
  • Успешная проверка выводит PASS, а неудачная — FAIL с ожидаемым и фактическим значениями, что является диагностикой, которую дают вам сообщения проверок JUnit.
  • Намеренно неверный случай (expected 10 but got 5) показывает, как выглядит красный тест: обвязчик продолжает выполнять оставшиеся проверки вместо того, чтобы остановиться на первой ошибке.
  • Итоговый отчёт показывает 5 всего, 4 успешно, 1 неудача — тот же отчёт об успехах и неудачах, который выводит раннер тестов в конце выполнения.
  • Поскольку один тест провалился, программа завершается с BUILD FAILURE, демонстрируя, почему один сломанный тест должен ломать весь билд в CI.

Как всё это соединяется

Инструменты тестирования Java выстраиваются в слои друг над другом — от простых проверок до полной интеграции со сборкой:

  1. Проверки (assertEquals, assertThrows) формулируют, что должно быть истинным.
  2. JUnit обнаруживает и запускает методы @Test и сообщает о результатах.
  3. Mockito предоставляет поддельные зависимости, чтобы можно было тестировать модуль в изоляции.
  4. Maven или Gradle встраивает набор тестов в процесс сборки, проваливая её при любом красном тесте.
  5. CI запускает сборку при каждом коммите, чтобы сломанный код никогда не попал в основную ветку.

Каждая последующая глава посвящена одной ступени этой лестницы — сначала аннотации и проверки JUnit, затем имитация с Mockito, затем интеграция тестов в Maven и Gradle. Понимание места каждого инструмента помогает сохранить целостность всей картины тестирования.

Практика

Практика
В паттерне расставить-выполнить-проверить, что делает шаг 'проверить'?
В паттерне расставить-выполнить-проверить, что делает шаг 'проверить'?
Was this page helpful?