Статьи

Очистить параметризованные тесты с помощью JUnit 5

Общая идея параметризованных модульных тестов — запускать один и тот же метод тестирования для разных данных. Создание параметризованных тестов в JUnit 4 далеко от совершенства. Существует много проблем с существующей архитектурой: параметры определяются как поля класса, и для их создания требуется конструктор, параметризованные и непараметрические тесты нельзя смешивать в одном классе тестов, а встроенные источники данных очень ограничены. К счастью, все это улучшено в JUnit 5!

Примечание. В качестве альтернативы параметризованному тесту JUnit 4 вы можете использовать библиотеку JUnitParams, которая решает многие из упомянутых мною проблем (см. Мой пост в блоге о JUnitParams здесь: http://blog.codeleak.pl/2013/12/parametrized-junit- tests-with.html ).

С чего начать?

Чтобы начать работу с параметризованными тестами в Junit 5, вам необходимо добавить в проект необходимую зависимость: добавьте org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion} зависимость от проекта для использования параметризованных тестов, поставщиков аргументов и конвертеров. ,

SUT — тестируемая система

Все созданные мной образцы тестируют класс FizzBuzz:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class FizzBuzz {
 
    private static final int THREE = 3;
    private static final int FIVE = 5;
 
    public String calculate(int number) {
        if (isDivisibleBy(number, THREE) && isDivisibleBy(number, FIVE)) {
            return "FizzBuzz";
        }
 
        if (isDivisibleBy(number, THREE)) {
            return "Fizz";
        }
 
        if (isDivisibleBy(number, FIVE)) {
            return "Buzz";
        }
 
        return String.valueOf(number);
    }
 
    private static boolean isDivisibleBy(int dividend, int divisor) {
        return dividend % divisor == 0;
    }
}

Хотя FizzBuzz действительно прост, его также можно использовать для демонстрации более продвинутых методов модульного тестирования, таких как реализация параметризованных тестов.

Мой первый параметризованный тест в JUnit 5

Чтобы создать параметризованный тест в JUnit 5, аннотируйте метод теста с помощью @org.junit.jupiter.params.ParameterizedTest (вместо @Test ) и предоставьте источник аргумента:

1
2
3
4
5
@ParameterizedTest(name = "{index} => calculate({0})")
@ValueSource(ints = {1, 2, 4, 7, 11, 13, 14})
public void returnsNumberForNumberNotDivisibleByThreeAndFive(int number, TestInfo testInfo) {
    assertThat(fizzBuzz.calculate(number)).isEqualTo("" + number);
}

У аннотации есть необязательный атрибут name который используется для настройки отображаемых имен вызовов. Доступные переменные шаблона: {index} -> текущий индекс вызова (на основе 1), {arguments} -> полный список аргументов через запятую, {0}, {1},… -> отдельный аргумент.

В этом примере @org.junit.jupiter.params.provider.ValueSource предоставляет доступ к массиву литеральных значений целых чисел. В этой аннотации должен быть указан только один тип ввода (строки, вставки, длинные или двойные).

Я также предоставляю дополнительные параметры, разрешенные с помощью org.junit.jupiter.api.extension.ParameterResolver . Обратите внимание, что параметры метода, которые разрешаются источниками аргументов, должны стоять на первом месте в списке аргументов.

Больше источников аргументов

@MethodSource

1
2
3
4
5
@ParameterizedTest(name = "{index} => calculate({0})")
@MethodSource(names = {"divisibleByThree", "divisibleByThreeButNotFive"})
void returnFizzForNumberDivisibleByThree(int number) {
    assertThat(fizzBuzz.calculate(number)).isEqualTo("Fizz");
}

@org.junit.jupiter.params.provider.MethodSource ссылается на методы (1 или более), возвращающие источник аргумента. В этом примере есть два метода:

1
2
3
4
5
6
7
8
9
private static Stream<Integer> divisibleByThree() {
    int[] ints = new int[]{18, 21};
    return Stream.of(3, 6, 9, 12);
}
 
// The returned array will be converted to a Stream
private static String[] divisibleByThreeButNotFive() {
    return new String[]{"18", "21"};
}

Метод, который предоставляет аргументы, должен быть статическим, не должен принимать аргументов и должен возвращать либо Stream, Iterable, Iterator, либо массив. Вероятно, вы заметили, что метод divisibleByThreeButNotFive() возвращает массив строк. Это будет прекрасно работать благодаря встроенным конвертерам неявных аргументов . Это действительно полезно, когда источником аргумента является CSV (подробнее об этом ниже). Кроме того, аргументы можно конвертировать с помощью пользовательских конвертеров аргументов.

Для разрешения нескольких аргументов источник метода возвратит поток экземпляров org.junit.jupiter.params.provider.ObjectArrayArguments ( org.junit.jupiter.params.provider.ObjectArrayArguments ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@ParameterizedTest(name = "{index} => calculate({0}) should return {1}")
@MethodSource(names = {"fizzBuzz"})
void fizzBuzz(int number, String expectedResult) {
    assertThat(fizzBuzz.calculate(number)).isEqualTo(expectedResult);
}
 
private static Stream<Arguments> fizzBuzz() {
    return Stream.of(
        ObjectArrayArguments.create(1, "1"),
        ObjectArrayArguments.create(2, "2"),
        ObjectArrayArguments.create(3, "Fizz"),
        ObjectArrayArguments.create(4, "4"),
        ObjectArrayArguments.create(5, "Buzz"),
        ObjectArrayArguments.create(6, "Fizz"),
        ObjectArrayArguments.create(7, "7"),
        ObjectArrayArguments.create(8, "8"),
        ObjectArrayArguments.create(9, "Fizz"),
        ObjectArrayArguments.create(15, "FizzBuzz")
    );
}

@CsvFileSource

Другой очень интересный способ предоставления источника аргумента — org.junit.jupiter.params.provider.CsvFileSource который предоставляет аргументы из одного или нескольких CSV-файлов из classpath:

1
2
3
4
5
@ParameterizedTest(name = "{index} => calculate({0}) should return {1}")
@CsvFileSource(resources = {"/fizzbuzz/fizzbuzz_1.csv", "/fizzbuzz/fizzbuzz_2.csv"}, delimiter = ';')
void fizzBuzzCsv(int number, String expectedResult) {
    assertThat(fizzBuzz.calculate(number)).isEqualTo(expectedResult);
}

Другие источники аргументов

  • @EnumSource предоставляет удобный способ использования констант Enum.
  • @CsvSource позволяет вам выражать списки аргументов как значения, разделенные запятыми
  • @ArgumentsSource может использоваться для указания настраиваемого поставщика аргументов для повторного использования.

Наслаждайтесь параметризованными тестами в JUnit 5!

Ресурсы

Все примеры, представленные в этой статье, можно найти на GitHub: https://github.com/kolorobot/unit-testing-demo.

Посмотрите официальную документацию JUnit 5, чтобы узнать больше: http://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests