W3docs

Введение в Java JUnit

Что такое JUnit, как добавить его в проект Java и как написать первый тест с JUnit.

JUnit — это стандартный фреймворк для написания автоматических тестов в Java. Тест — это небольшой метод, который запускает часть вашего кода и утверждает, что он работает ожидаемым образом; задача JUnit — найти эти методы, запустить каждый в изоляции, проверить утверждения и сообщить, какие прошли, а какие нет. Текущее поколение, JUnit 5 (также называемое JUnit Jupiter), поставляется в виде набора небольших библиотек, которые добавляются в сборку — оно не является частью JDK — и управляет шагом mvn test / gradle test, который является обязательным условием почти в каждом CI Java-проекта.

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

Можно проверять код вручную с помощью методов main и println — но это плохо масштабируется. Фреймворк даёт вам четыре вещи, которые иначе пришлось бы реализовывать самому:

  • Обнаружение — он автоматически находит каждый метод @Test; вам не нужно поддерживать список вручную.
  • Изоляция — каждый тест получает свежую фикстуру, поэтому один тест не может нарушить другой.
  • Утверждения — богатый словарь (assertEquals, assertThrows, …), который выдаёт точные сообщения об ошибках.
  • Отчётность — единый итог прохождения/провала, понятный инструменту сборки и IDE.

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

Добавление JUnit в проект

JUnit 5 — это зависимость, объявленная в файле сборки. В Maven агрегатор junit-jupiter подтягивает API (для компиляции) и движок (для запуска):

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter</artifactId>
  <version>5.11.3</version>
  <scope>test</scope>
</dependency>

В Gradle это две строки плюс переключатель useJUnitPlatform():

dependencies {
  testImplementation 'org.junit.jupiter:junit-jupiter:5.11.3'
}
test {
  useJUnitPlatform()
}

Исходные коды тестов располагаются в src/test/java, зеркально повторяя пакет тестируемого класса. Область test гарантирует, что JUnit не попадёт в производственный артефакт.

Ваш первый тест

Тест JUnit — это обычный метод, аннотированный @Test. Внутри него вы вызываете тестируемый код и проверяете результат. Вот Calculator и класс тестов для него:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {

  private final Calculator calc = new Calculator();

  @Test
  void addReturnsSum() {
    assertEquals(5, calc.add(2, 3));
  }

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

Обратите внимание на паттерн, которому следует каждый тест: Arrange — подготовьте фикстуру, Act — вызовите метод, Assert — проверьте результат. Тестовые методы имеют package-private доступ (в JUnit 5 public не нужен) и возвращают void. Запустите mvn test, и сборка станет зелёной только при выполнении всех утверждений.

Аннотации и утверждения, которые используются чаще всего

Поверхность JUnit небольшая. Несколько членов покрывают подавляющее большинство реальных тестов:

ЭлементПакет / классНазначение
@Testorg.junit.jupiter.apiПомечает метод как тест
@BeforeEach / @AfterEachorg.junit.jupiter.apiВыполняется до/после каждого теста (настройка / сброс фикстур)
@BeforeAll / @AfterAllorg.junit.jupiter.apiВыполняется один раз до/после всех тестов в классе
@DisplayNameorg.junit.jupiter.apiЧеловекочитаемое имя для отчётов
@Disabledorg.junit.jupiter.apiВременно пропустить тест
assertEquals(exp, act)AssertionsПровал, если два значения не равны
assertTrue / assertFalseAssertionsПровал, если boolean не выполняется
assertThrows(type, exec)AssertionsПровал, если лямбда не бросает то исключение
assertNull / assertNotNullAssertionsПровал при неверном значении null

@BeforeEach обеспечивает каждому тесту чистое состояние — JUnit создаёт новый экземпляр тестового класса для каждого @Test, а затем выполняет настройку, поэтому состояние никогда не утекает между тестами.

Что JUnit делает за вас, в одном запускаемом файле

В коде ниже нет JUnit в classpath (это внешняя библиотека, не часть JDK), поэтому пример переимплементирует основной цикл JUnit на чистом Java: фикстура пересоздаётся перед каждым тестом, небольшой набор вспомогательных assertXxx, список тестовых методов, выполняемых независимо, и итоговый счётчик прошедших/проваленных. Именно это и автоматизирует JUnit — видя это без обёрток, вы сразу понимаете реальный фреймворк. Один тест намеренно проваливается, чтобы вы увидели, как выглядит «красный» результат.

java— editable, runs on the server

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

  • Три правильных теста печатают PASS, а четвёртый печатает FAIL deliberatelyFailing -> expected <10> but was <5>точное сообщение об ошибке, а не просто «тест не прошёл». Эта разница (expected … but was …) — именно то, что даёт assertEquals в JUnit, и именно это позволяет диагностировать красный тест с первого взгляда.
  • setUp() выполняется перед каждым тестом, поэтому calc — это свежий Calculator каждый раз. Это контракт @BeforeEach: тесты изолированы, и порядок их выполнения никогда не может иметь значения, потому что ни один из них не разделяет изменяемое состояние.
  • divideThrowsOnZero проходит, утверждая, что исключение было брошено — assertThrows превращает «здесь должна быть ошибка» в первоклассное, позитивное утверждение вместо хрупкого try/catch. Ожидаемые исключения — это поведение, достойное тестирования, а не ошибки, которые нужно замалчивать.
  • Итоговый подсчёт — Tests run: 4, Passed: 3, Failed: 1, Assertions: 5 — это отчёт. Один провалившийся тест из четырёх всё равно переключает всю сборку в RED; CI рассматривает любой сбой как остановку, именно поэтому зелёный набор тестов имеет смысл.
  • Здесь ничего не импортировало JUnit, однако форма идентична: обнаружение методов-аннотаций, настройка перед каждым тестом, утверждения, итог. Ценность JUnit в том, что он автоматизирует этот цикл (и добавляет обнаружение, параллелизм, параметризованные тесты и интеграцию с IDE), так что вы пишете только тела тестов.

Что охватывает остальная часть этого раздела

Этот раздел строится на основе цикла, который вы только что увидели:

Если перед погружением в API вы хотите понять, зачем вообще нужно автоматическое тестирование, см. Тестирование в Java. В противном случае следующая глава начинается там, где начинается каждый набор тестов: определение тестового класса и его запуск.

Практика

Практика
Что гарантирует аннотация @BeforeEach в JUnit 5 относительно тестовой фикстуры?
Что гарантирует аннотация @BeforeEach в JUnit 5 относительно тестовой фикстуры?
Was this page helpful?