«1. Обзор
JUnit 5 имеет хорошую поддержку для настройки имен тестовых классов и тестовых методов. В этом кратком руководстве мы увидим, как мы можем использовать настраиваемые генераторы отображаемых имен JUnit 5 с помощью аннотации @DisplayNameGeneration.
2. Генерация отображаемых имен
Мы можем настроить собственные генераторы отображаемых имен с помощью аннотации @DisplayNameGeneration. Однако полезно знать, что аннотация @DisplayName всегда имеет приоритет над любым генератором отображаемого имени.
Для начала JUnit 5 предоставляет класс DisplayNameGenerator.ReplaceUnderscores, который заменяет любые символы подчеркивания в именах пробелами. Давайте рассмотрим пример:
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class ReplaceUnderscoresGeneratorUnitTest {
@Nested
class when_doing_something {
@Test
void then_something_should_happen() {
}
@Test
@DisplayName("@DisplayName takes precedence over generation")
void override_generator() {
}
}
}
Теперь, когда мы запустим тест, мы увидим, что генерация отображаемого имени сделала вывод теста более читабельным:
└─ ReplaceUnderscoresGeneratorUnitTest ✓
└─ when doing something ✓
├─ then something should happen() ✓
└─ @DisplayName takes precedence over generation ✓
3. Пользовательский генератор отображаемых имен
Чтобы написать собственный генератор отображаемых имен, нам нужно написать класс, реализующий методы интерфейса DisplayNameGenerator. В интерфейсе есть методы для генерации имени класса, вложенного класса и метода.
3.1. Замена верблюжьего регистра
Начнем с простого генератора отображаемых имен, который заменяет имена верблюжьих регистров читаемыми предложениями. Для начала мы можем расширить класс DisplayNameGenerator.Standard:
static class ReplaceCamelCase extends DisplayNameGenerator.Standard {
@Override
public String generateDisplayNameForClass(Class<?> testClass) {
return replaceCamelCase(super.generateDisplayNameForClass(testClass));
}
@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return replaceCamelCase(super.generateDisplayNameForNestedClass(nestedClass));
}
@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
return this.replaceCamelCase(testMethod.getName()) +
DisplayNameGenerator.parameterTypesAsString(testMethod);
}
String replaceCamelCase(String camelCase) {
StringBuilder result = new StringBuilder();
result.append(camelCase.charAt(0));
for (int i=1; i<camelCase.length(); i++) {
if (Character.isUpperCase(camelCase.charAt(i))) {
result.append(' ');
result.append(Character.toLowerCase(camelCase.charAt(i)));
} else {
result.append(camelCase.charAt(i));
}
}
return result.toString();
}
}
В приведенном выше примере мы видим методы, генерирующие разные части отображаемого имени.
Давайте напишем тест для нашего генератора:
@DisplayNameGeneration(DisplayNameGeneratorUnitTest.ReplaceCamelCase.class)
class DisplayNameGeneratorUnitTest {
@Test
void camelCaseName() {
}
}
Далее, при запуске теста мы видим, что названия верблюжьих падежей были заменены читаемыми предложениями:
└─ Display name generator unit test ✓
└─ camel case name() ✓
3.2. Ориентировочные предложения
До сих пор мы обсуждали очень простые варианты использования. Однако мы можем пойти более творчески:
static class IndicativeSentences extends ReplaceCamelCase {
@Override
public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
return super.generateDisplayNameForNestedClass(nestedClass) + "...";
}
@Override
public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
return replaceCamelCase(testClass.getSimpleName() + " " + testMethod.getName()) + ".";
}
}
Идея здесь состоит в том, чтобы создать показательные предложения из вложенного класса и тестового метода. Другими словами, имя вложенного класса будет добавлено к имени тестового метода:
class DisplayNameGeneratorUnitTest {
@Nested
@DisplayNameGeneration(DisplayNameGeneratorUnitTest.IndicativeSentences.class)
class ANumberIsFizz {
@Test
void ifItIsDivisibleByThree() {
}
@ParameterizedTest(name = "Number {0} is fizz.")
@ValueSource(ints = { 3, 12, 18 })
void ifItIsOneOfTheFollowingNumbers(int number) {
}
}
@Nested
@DisplayNameGeneration(DisplayNameGeneratorUnitTest.IndicativeSentences.class)
class ANumberIsBuzz {
@Test
void ifItIsDivisibleByFive() {
}
@ParameterizedTest(name = "Number {0} is buzz.")
@ValueSource(ints = { 5, 10, 20 })
void ifItIsOneOfTheFollowingNumbers(int number) {
}
}
}
Глядя на пример, мы используем вложенный класс в качестве контекста для тестового метода. Чтобы лучше проиллюстрировать результаты, давайте запустим тест:
└─ Display name generator unit test ✓
├─ A number is buzz... ✓
│ ├─ A number is buzz if it is one of the following numbers. ✓
│ │ ├─ Number 5 is buzz. ✓
│ │ ├─ Number 10 is buzz. ✓
│ │ └─ Number 20 is buzz. ✓
│ └─ A number is buzz if it is divisible by five. ✓
└─ A number is fizz... ✓
├─ A number is fizz if it is one of the following numbers. ✓
│ ├─ Number 3 is fizz. ✓
│ ├─ Number 12 is fizz. ✓
│ └─ Number 18 is fizz. ✓
└─ A number is fizz if it is divisible by three. ✓
Как мы видим, генератор объединил имена вложенных классов и тестовых методов для создания показательных предложений.
4. Заключение
В этом руководстве мы увидели, как использовать аннотацию @DisplayNameGeneration для создания отображаемых имен для наших тестов. Кроме того, мы написали собственный DisplayNameGenerator для настройки генерации отображаемого имени.
Как обычно, примеры, использованные в этой статье, можно найти в проекте GitHub.