Введение в JUnit 5
JUnit 5 — это современный фреймворк для модульного тестирования (unit testing) на языке Java. Он является эволюционным развитием популярного JUnit 4 и входит в экосистему Java как стандарт де-факто для написания автоматизированных тестов. JUnit 5 — это не просто обновление, а полностью переработанная архитектура, состоящая из трёх подпроектов: JUnit Platform (запуск тестов), JUnit Jupiter (новая модель программирования и расширений) и JUnit Vintage (обратная совместимость с JUnit 3/4).
Зачем нужен JUnit 5? В современной разработке (особенно в методологиях TDD — Test-Driven Development) тестирование — это не опция, а обязательный этап. JUnit 5 позволяет:
- Автоматизировать проверку кода: Запускать сотни тестов одной командой.
- Обеспечить регрессионную надёжность: Убедиться, что новый код не сломал старую функциональность.
- Улучшить дизайн кода: Тестируемый код часто оказывается более модульным и слабо связанным.
- Документировать поведение: Тесты служат живой документацией, показывающей, как должен работать класс или метод.
- Интегрироваться в CI/CD: JUnit 5 легко встраивается в Maven, Gradle, Jenkins, GitHub Actions.
Ключевые нововведения JUnit 5 по сравнению с JUnit 4: аннотации нового поколения (@Test, @ParameterizedTest, @Nested), мощная система расширений (Extensions), поддержка лямбда-выражений и потоковых API, а также встроенная поддержка параметризованных и динамических тестов.
Установка JUnit 5
Поскольку Java — это компилируемый язык, установка производится через менеджеры зависимостей (Maven или Gradle). JUnit 5 не устанавливается через pip или npm. Ниже приведены инструкции для двух самых популярных систем сборки.
Установка через Maven (pom.xml)
Добавьте следующие зависимости в ваш файл pom.xml. Рекомендуется использовать последнюю стабильную версию (на момент написания — 5.11.0, проверьте актуальную на MVNRepository).
<dependencies>
<!-- Основной модуль JUnit Jupiter (включает API, Engine и параметризацию) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- (Опционально) Для совместимости с JUnit 4 -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Установка через Gradle (build.gradle)
Добавьте в секцию dependencies:
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0'
// (Опционально) Для JUnit 4
testImplementation 'org.junit.vintage:junit-vintage-engine:5.11.0'
}
test {
useJUnitPlatform() // Обязательно для включения JUnit 5
}
Установка без системы сборки (Plain Java)
Если вы не используете Maven/Gradle, скачайте JAR-файлы с официального репозитория и добавьте их в classpath вашего проекта. Однако это крайне не рекомендуется для реальных проектов.
Быстрый старт: первый тест на JUnit 5
Создадим простой класс Calculator и напишем для него тест.
1. Класс для тестирования (Calculator.java):
public class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Делить на ноль нельзя");
}
return a / b;
}
}
2. Тестовый класс (CalculatorTest.java):
Тестовый класс должен находиться в той же структуре пакетов, но в директории src/test/java.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private final Calculator calculator = new Calculator();
@Test
void testAddition() {
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 должно быть равно 5");
}
@Test
void testDivisionByZero() {
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
calculator.divide(10, 0);
});
assertEquals("Делить на ноль нельзя", exception.getMessage());
}
}
3. Запуск:
- В IntelliJ IDEA: нажмите зелёный треугольник рядом с именем класса или метода.
- В командной строке (Maven):
mvn test - В командной строке (Gradle):
gradle test
В консоли вы увидите зелёный отчёт: тесты пройдены (PASSED).
Основные методы и аннотации JUnit 5
Рассмотрим 10 ключевых аннотаций и методов, которые покрывают 95% потребностей в тестировании.
1. @Test
Что делает: Помечает метод как тестовый. JUnit Platform находит и запускает все методы с этой аннотацией. В отличие от JUnit 4, метод не обязан быть публичным.
Параметры: Не принимает параметров (кроме необязательного имени отображения).
Пример:
@Test
@DisplayName("Проверка сложения положительных чисел")
void shouldAddPositiveNumbers() {
assertEquals(4, 2 + 2);
}
2. assertEquals
Что делает: Проверяет, что два значения равны (через метод equals()). Если нет — тест падает с AssertionError.
Параметры:
expected(Object) — ожидаемое значение.actual(Object) — фактическое значение.message(String, опционально) — сообщение об ошибке.
Пример:
@Test
void testStringEquality() {
String actual = "Hello".toUpperCase();
assertEquals("HELLO", actual, "Строка должна быть в верхнем регистре");
}
3. assertThrows
Что делает: Проверяет, что выполнение переданного лямбда-выражения выбрасывает исключение указанного типа. Возвращает объект исключения для дальнейшей проверки.
Параметры:
expectedType(Class<T>) — класс ожидаемого исключения.executable(Executable) — лямбда с кодом, который должен выбросить исключение.
Пример:
@Test
void testThrowsOnNullInput() {
assertThrows(NullPointerException.class, () -> {
String str = null;
str.length();
});
}
4. assertTrue / assertFalse
Что делает: Проверяет, что булево условие истинно (assertTrue) или ложно (assertFalse).
Параметры:
condition(boolean) — проверяемое условие.message(String, опционально) — сообщение об ошибке.
Пример:
@Test
void testIsPositive() {
int number = 5;
assertTrue(number > 0, "Число должно быть положительным");
assertFalse(number < 0);
}
5. assertNull / assertNotNull
Что делает: Проверяет, что объект является null (assertNull) или не null (assertNotNull).
Параметры:
actual(Object) — проверяемый объект.message(String, опционально) — сообщение об ошибке.
Пример:
@Test
void testUserCreation() {
User user = new User("Alice");
assertNotNull(user.getId(), "ID пользователя не должен быть null после создания");
}
6. assertArrayEquals
Что делает: Проверяет, что два массива равны (одинаковая длина и поэлементное равенство).
Параметры:
expected(int[] / Object[] / etc.) — ожидаемый массив.actual(int[] / Object[] / etc.) — фактический массив.
Пример:
@Test
void testArraySort() {
int[] unsorted = {3, 1, 2};
Arrays.sort(unsorted);
assertArrayEquals(new int[]{1, 2, 3}, unsorted);
}
7. @BeforeEach / @AfterEach
Что делает: Метод, помеченный @BeforeEach, выполняется перед каждым тестовым методом. @AfterEach — после каждого. Используется для инициализации и очистки ресурсов (например, создание нового объекта).
Параметры: Нет.
Пример:
private List<String> list;
@BeforeEach
void setUp() {
list = new ArrayList<>();
}
@AfterEach
void tearDown() {
list.clear(); // Очистка после каждого теста
}
@Test
void testAddItem() {
list.add("A");
assertEquals(1, list.size());
}
@Test
void testEmptyList() {
assertTrue(list.isEmpty());
}
8. @BeforeAll / @AfterAll
Что делает: Метод выполняется один раз до всех тестов в классе (@BeforeAll) и один раз после всех (@AfterAll). Должен быть статическим (static). Используется для настройки дорогих ресурсов (например, подключение к базе данных).
Параметры: Нет.
Пример:
private static DatabaseConnection connection;
@BeforeAll
static void initAll() {
connection = new DatabaseConnection("jdbc:h2:mem:test");
connection.connect();
}
@AfterAll
static void tearDownAll() {
connection.disconnect();
}
@Test
void testDatabaseQuery() {
assertTrue(connection.isConnected());
}
9. @ParameterizedTest
Что делает: Позволяет запускать один и тот же тест с разными наборами аргументов. Требует указания источника данных (например, @ValueSource, @CsvSource).
Параметры: Зависит от источника (см. пример).
Пример с @ValueSource:
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testIsPositiveWithMultipleValues(int number) {
assertTrue(number > 0);
}
Пример с @CsvSource:
@ParameterizedTest
@CsvSource({
"2, 3, 5",
"0, 0, 0",
"-1, 1