Параметризованные тесты
Часто одну проверку нужно повторить для многих входных данных: слаг для десяти разных заголовков, отказ для пустого, пробельного и null-заголовка. Копировать тест десять раз — плохая идея. Параметризованный тест пишется один раз, получает данные через параметры метода, и JUnit запускает его по разу для каждого набора значений.
Тестируемый класс
Слаг (slug) — часть адреса поста, построенная из заголовка: Hello, World! превращается в hello-world. Создайте src/main/java/com/example/blog/Slugs.java:
// src/main/java/com/example/blog/Slugs.java
package com.example.blog;
import java.util.Locale;
public final class Slugs {
private Slugs() {
}
public static String of(String title) {
return title.toLowerCase(Locale.ROOT)
.replaceAll("[^a-z0-9]+", "-")
.replaceAll("^-|-$", "");
}
}Всё, кроме латинских букв и цифр, превращается в дефис; дефисы по краям удаляются. Кириллицу этот простой вариант не поддерживает, поэтому в примерах заголовки латиницей.
@ValueSource
Вместо @Test пишут @ParameterizedTest и добавляют источник данных. Самый простой — @ValueSource, массив значений одного типа:
// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@ValueSource(strings = {"Hello", "Hello, World!", " Java 25 "})
void slugHasNoSpaces(String title) {
assertFalse(Slugs.of(title).contains(" "));
}Тест выполнится три раза, каждый раз с другим title. Кроме strings есть ints, longs, doubles и другие массивы. Аннотации импортируются из org.junit.jupiter.params и org.junit.jupiter.params.provider — зависимость junit-jupiter их уже содержит.
@CsvSource
Когда тесту нужно несколько параметров — вход и ожидаемый результат, — используйте @CsvSource. Каждая строка — один запуск, значения разделены запятыми:
// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@CsvSource({
"Hello, hello",
"'Hello, World!', hello-world",
"' Java 25 ', java-25",
"JUnit & Mockito, junit-mockito"
})
void slugFromTitle(String title, String expected) {
assertEquals(expected, Slugs.of(title));
}Пробелы вокруг значений обрезаются, поэтому столбцы можно выравнивать. Значение с запятой или со значимыми пробелами берут в одинарные кавычки. JUnit сам преобразует строки в типы параметров: int, long, boolean, BigDecimal, перечисления.
Пустые значения и null
@NullAndEmptySource добавляет два запуска: с null и с пустой строкой. Вместе с @ValueSource получаются сразу все случаи «ничего»:
// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t"})
void blankTitleIsRejected(String title) {
PostService service = new PostService(new InMemoryPostRepository(), (to, message) -> { });
assertThrows(IllegalArgumentException.class, () -> service.create(title, "anna"));
}Четыре запуска: null, "", пробел и табуляция. Три теста класса вместе дают в отчёте Maven Tests run: 11.
Имена запусков
IDE показывает каждый запуск отдельно. JUnit 6 берёт строковые значения в кавычки, чтобы пустые и пробельные строки были видны: запуски blankTitleIsRejected называются [1] null, [2] "", [3] " " и [4] "\t". Свой формат задаёт параметр name:
// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest(name = "слаг для {0} — {1}"){0}, {1} — параметры, {index} — номер запуска. Теперь запуски называются слаг для "Hello" — "hello" и так далее. В отчётах Maven запуски называются по методу и номеру: slugFromTitle(String, String)[2].
Частая ошибка
Если забыть кавычки вокруг значения с запятой, строка разобьётся на три столбца. JUnit 6 не жалуется на лишний столбец, а просто берёт первые два:
"Hello, World!, hello-world"[ERROR] com.example.blog.CsvFailTest.slugFromTitle(String, String)[2] -- Time elapsed: 0.015 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <World!> but was: <hello>Заголовком стало Hello, а ожидаемым значением — World!. Значения с запятыми всегда берите в одинарные кавычки: 'Hello, World!'.