Начните с JUnit 5 модульного тестирования Java

Java

предисловие

Модульное тестирование — неотъемлемая часть разработки программного обеспечения, но в обычной разработке его часто предпочитают игнорировать из-за плотного проектного цикла и большой рабочей нагрузки, что часто приводит к бесконечным проблемам с программным обеспечением. На самом деле, многие проблемы, которые появляются в сети, можно обнаружить и своевременно решить с помощью модульных тестов, поэтому необходимо развивать свою способность писать модульные тесты в повседневной разработке. Независимо от того, хотите ли вы улучшить свои навыки кодирования или улучшить качество проекта, это принесет большую пользу.Эта статья познакомит вас с основами понимания и использования среды модульного тестирования Java JUnit 5 для написания модульных тестов.Я надеюсь, что она также поможет вам .

Все фрагменты кода, задействованные в этой статье, находятся на следующем складе. Заинтересованные партнеры могут ознакомиться с ними и изучить их:

GitHub.com/ Как будто впервые увидел 12138…

Поддержка версий:

  • JDK 8
  • JUnit 5.5.2
  • Lomok 1.18.8

Знакомство с JUnit 5.

Чтобы говорить о JUnit 5, мы должны сначала поговорить о среде модульного тестирования Java JUnit.Он и другая среда, TestNG, занимают основной рынок сред модульного тестирования в области Java.Среди них JUnit имеет долгую историю развития и богатые functions. , предпочитаемый большинством разработчиков Java.

Что касается истории JUnit, JUnit возник в 1997 г. Первоначальная версия была завершена двумя мастерами программирования Кентом Беком и Эрихом Гаммой во время полета на самолете.Из-за отсутствия зрелых инструментов в процессе тестирования Java в то время, два были в самолете.Что касается совместной разработки и реализации прототипа JUnit, он стремится стать лучшей средой тестирования Java. По прошествии более 20 лет JUnit итеративно развивался через различные версии и был разработан до версии 5.x, обеспечивающей лучшую поддержку (например, поддержку Lambda) и более богатые формы тестирования (например, JDK 8 и более поздние версии). , параметризованные тесты).

Узнав о JUint, давайте вернемся к JUnit 5. Эту версию можно назвать крупным обновлением среды модульного тестирования JUnit. Во-первых, требуется работающая среда выше Java 8. Хотя старую версию JDK также можно скомпилировать и запустить, он должен быть полностью использован.Функциональность JUnit 5, среда JDK 8 имеет важное значение.

Помимо этого, JUnit 5 отличается от предыдущих версий JUnit тем, что он разделен на несколько разных модулей, состоящих из трех разных подпроектов.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

  • JUnit Platform: базовая служба, используемая для запуска среды тестирования на JVM, обеспечивающая поддержку выполнения тестов в командной строке, среде IDE и инструментах сборки.

  • JUnit Jupiter: Содержит новую модель программирования и модель расширения JUnit 5, в основном для написания тестового кода и кода расширения.

  • JUnit Vintage: для запуска тестовых случаев JUnit3.x и JUnit4.x в JUnit 5, совместимых.

Основываясь на приведенном выше введении, вы можете обратиться к следующему рисунку, чтобы понять архитектуру и модули JUnit 5:

Зачем вам JUnit 5

После разговора о том, что такое JUnit 5, давайте подумаем над другим вопросом: зачем нам нужен JUnit 5?

С появлением таких сред тестирования, как JUnit, область модульного тестирования Java постепенно созрела, и разработчики предъявляют более высокие требования к средам модульного тестирования: больше методов тестирования и меньше зависимостей от других библиотек. Поэтому все с нетерпением ждут рождения более мощной среды тестирования.Являясь лидером в области тестирования Java, компания JUnit выпустила версию JUnit 5. Основные функции:

  • Предоставляет новые утверждения и тестовые аннотации для поддержки встроенных тестовых классов.

  • Более богатые методы тестирования: поддержка динамического тестирования, повторного тестирования, параметризованного тестирования и т. д.

  • Реализована модульность, отделяющая различные модули, такие как выполнение тестов и обнаружение тестов, а также уменьшающая количество зависимостей.

  • Обеспечивает поддержку Java 8, например лямбда-выражений, Sream API и т. д.

Введение в общее использование JUnit 5

Далее давайте рассмотрим некоторые распространенные варианты использования JUni 5, которые помогут нам быстро освоить использование JUnit 5.

В первую очередь в проект Maven вводятся координаты зависимостей JUnit 5. Следует отметить, что текущая среда JDK должна быть выше Java 8.

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

первый тест

Представляя JUnit 5, мы можем быстро написать простой тестовый пример, из этого тестового примера понять начальный JUnit 5:

@DisplayName("我的第一个测试用例")
public class MyFirstTestCaseTest {

    @BeforeAll
    public static void init() {
        System.out.println("初始化数据");
    }

    @AfterAll
    public static void cleanup() {
        System.out.println("清理数据");
    }

    @BeforeEach
    public void tearup() {
        System.out.println("当前测试方法开始");
    }

    @AfterEach
    public void tearDown() {
        System.out.println("当前测试方法结束");
    }

    @DisplayName("我的第一个测试")
    @Test
    void testFirstTest() {
        System.out.println("我的第一个测试开始测试");
    }

    @DisplayName("我的第二个测试")
    @Test
    void testSecondTest() {
        System.out.println("我的第二个测试开始测试");
    }
}

Запустите этот тестовый пример напрямую, вы можете увидеть журнал консоли следующим образом:

Вы можете видеть, что имя элемента теста, отображаемое в результатах в левом столбце, — это то, что мы используем в тестовом классе и методе.@DisplayNameИмя параметра. Эта аннотация введена в JUnit 5 для определения тестового класса и указания отображаемого имени варианта использования в отчете о тестировании. Эту аннотацию можно использовать в классах и методах. Использование ее в классе указывает, что class является тестовым Class, используемый в методе, указывает, что метод является тестовым методом.

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.0")
public @interface DisplayName {
	String value();
}

Давайте взглянем на пару аннотаций, используемых в примере кода **@BeforeAll ** и@AfterAll , они определяют работу всего тестового класса до и в конце и могут изменять только статические методы, которые в основном используются для инициализации и очистки глобальных данных и внешних ресурсов, необходимых в процессе тестирования. В отличие от них,@BeforeEachи@AfterEachПомеченный метод будет выполняться до и в конце каждого метода тестового примера, в основном отвечая за подготовку и уничтожение рабочей среды, необходимой для тестового примера.

В дополнение к этим основным аннотациям в процессе тестирования есть более богатые и мощные аннотации, давайте изучим их одну за другой.

Отключить выполнение тестов: @Disabled

Когда мы хотим пропустить тестовый метод и запустить другие тестовые случаи в обычном режиме при запуске тестового класса, мы можем использовать аннотацию @Disabled, чтобы указать, что тестовый метод недоступен и не будет выполняться при выполнении тестового метода тестового класса. Выполнение JUnit.

Давайте посмотрим на эффект запуска после использования @Disbaled и добавим следующий код в исходный тестовый класс:

@DisplayName("我的第三个测试")
@Disabled
@Test
void testThirdTest() {
	System.out.println("我的第三个测试开始测试");
}

После запуска вы можете увидеть журнал консоли следующим образом.Метод, помеченный @Disabled, не будет выполнен, и будет напечатана только информация об отдельном методе:

@Disabled также можно использовать в классах, чтобы пометить все тестовые методы в классе от выполнения, что обычно используется при объединении тестов для нескольких тестовых классов.

Вложенный тестовый класс: @Nested

По мере того, как мы будем писать все больше и больше классов и кодов, будет появляться все больше и больше соответствующих тестовых классов, которые необходимо протестировать. Чтобы решить проблему резкого увеличения числа тестовых классов, JUnit 5 предоставляет аннотацию @Nested, которая может логически группировать классы тестовых случаев в виде статических внутренних классов-членов. И каждый статический внутренний класс может иметь свои собственные методы жизненного цикла, которые будут выполняться в порядке от внешней к внутренней иерархии. Кроме того, вложенные классы также могут быть помечены @DisplayName, чтобы мы могли использовать правильное имя теста. Вот простое использование:

@DisplayName("内嵌测试类")
public class NestUnitTest {
    @BeforeEach
    void init() {
        System.out.println("测试方法执行前准备");
    }

    @Nested
    @DisplayName("第一个内嵌测试类")
    class FirstNestTest {
        @Test
        void test() {
            System.out.println("第一个内嵌测试类执行测试");
        }
    }

    @Nested
    @DisplayName("第二个内嵌测试类")
    class SecondNestTest {
        @Test
        void test() {
            System.out.println("第二个内嵌测试类执行测试");
        }
    }
}

После запуска всех тестовых случаев вы можете увидеть в консоли следующие результаты:

Повторный тест: @RepeatedTest

В JUnit 5 была добавлена ​​поддержка установки количества запусков тестового метода, что позволяет многократно запускать тестовый метод. Если вы хотите запустить тестовый метод N раз, вы можете пометить его @RepeatedTest, как показано в следующем коде:

@DisplayName("重复测试")
@RepeatedTest(value = 3)
public void i_am_a_repeated_test() {
	System.out.println("执行测试");
}

После запуска тестовый метод будет выполнен 3 раза, и эффект выполнения в IDEA показан на следующем рисунке:

Это основное использование, мы также можем изменить имя метода тестирования, который запускается повторно, используя встроенные переменные, предоставленные @RepeatedTest, в виде заполнителей в егоnameИспользуйте на свойствах, давайте посмотрим на использование и эффекты:

@DisplayName("自定义名称重复测试")
@RepeatedTest(value = 3, name = "{displayName} 第 {currentRepetition} 次")
public void i_am_a_repeated_test_2() {
	System.out.println("执行测试");
}

Используется внутри аннотации @RepeatedTestcurrentRepetitionПеременная представляет количество повторений,totalRepetitionsПеременная представляет собой общее количество повторений, которые необходимо повторить,displayNameПеременные представляют отображаемое имя метода тестирования, и мы можем использовать эти встроенные переменные для переопределения имени метода тестирования при его повторном запуске.

новое утверждение

Что касается дизайна API утверждений, в JUnit 5 были внесены значительные улучшения, и он в полной мере использует новые функции Java 8, особенно лямбда-выражения, и, наконец, предоставляет новые классы утверждений:org.junit.jupiter.api.Assertions. Многие методы утверждений принимают параметры лямбда-выражений.Одно из преимуществ использования лямбда-выражений в сообщениях утверждений заключается в том, что они вычисляются лениво, что может в определенной степени сэкономить время и ресурсы, если создание сообщения является дорогостоящим.

Теперь также можно сгруппировать несколько утверждений внутри метода, используя метод assertAll следующим образом:

@Test
void testGroupAssertions() {
    int[] numbers = {0, 1, 2, 3, 4};
    Assertions.assertAll("numbers",
            () -> Assertions.assertEquals(numbers[1], 1),
            () -> Assertions.assertEquals(numbers[3], 3),
            () -> Assertions.assertEquals(numbers[4], 4)
    );
}

Если какое-либо из сгруппированных утверждений не выполняется, будет выдана ошибка MultipleFailuresError.

Тест на тайм-аут: assertTimeoutPreemptively

Когда мы хотим проверить время выполнения трудоемкого метода и не хотим, чтобы тестовый метод ждал бесконечно, мы можем выполнить тест тайм-аута для тестового метода.JUnit 5 вводит метод утверждения.assertTimeout, который обеспечивает расширенную поддержку тайм-аутов.

Предположим, мы хотим, чтобы тестовый код выполнялся за одну секунду, мы можем написать следующий тестовый пример:

@Test
@DisplayName("超时方法测试")
void test_should_complete_in_one_second() {
  Assertions.assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> Thread.sleep(2000));
}

Этот тест не выполняется, потому что выполнение кода будет приостановлено на две секунды, и мы ожидаем, что тестовый пример завершится успешно в течение одной секунды. Однако, если мы установим время ожидания в одну секунду, тест все равно иногда будет давать сбои, потому что выполнение дополнительного кода и инструкций в дополнение к целевому коду во время выполнения тестового метода потребует времени, поэтому его нельзя сделано на пределе тайм-аута до точного точного совпадения с параметром времени.

Тестирование исключений: assertThrows

Для методов с исключениями в нашем коде мы обычно используем try-catch для их захвата и обработки.Для тестирования кода с выброшенными исключениями JUnit 5 предоставляет методыAssertions#assertThrows(Class<T>, Executable)Для тестирования первый параметр — это тип исключения, а второй — параметр функционального интерфейса. Как и в случае с интерфейсом Runnable, он не требует параметров и не возвращает значения, а также поддерживает использование лямбда-выражений. Подробнее см. следующий код:

@Test
@DisplayName("测试捕获的异常")
void assertThrowsException() {
  String str = null;
  Assertions.assertThrows(IllegalArgumentException.class, () -> {
    Integer.valueOf(str);
  });
}

Когда в коде в лямбда-выражении возникает исключение, оно будет сравниваться с типом исключения первого параметра.Если оно не принадлежит к тому же типу исключения, консоль выведет запрос, подобный следующему:org.opentest4j.AssertionFailedError: Unexpected exception type thrown ==> expected: <IllegalArgumentException> but was: <...Exception>

Параметризованные тесты JUnit 5

Чтобы использовать JUnit 5 для параметризованных тестов, в дополнение к базовой зависимости junit-jupiter-engine требуется еще одна зависимость модуля:junit-jupiter-params, который в основном предоставляет API для написания параметризованных тестов. Таким же образом импортируйте соответствующие зависимости той же версии в проект Maven:

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

Базовый тест источника данных: @ValueSource

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

public class ParameterizedUnitTest {
    @ParameterizedTest
    @ValueSource(ints = {2, 4, 8})
    void testNumberShouldBeEven(int num) {
        Assertions.assertEquals(0, num % 2);
    }

    @ParameterizedTest
    @ValueSource(strings = {"Effective Java", "Code Complete", "Clean Code"})
    void testPrintTitle(String title) {
        System.out.println(title);
    }
}

@ParameterizedTest заменяет аннотацию @Test как обязательную аннотацию для параметризованных тестов. Любой параметризованный метод тестирования должен быть помечен этой аннотацией.

Запустите тест, и результат показан на рисунке ниже. Целевой метод будет запущен для каждого параметра в @ValueSource. Если параметр не может запустить тест, это означает, что метод теста не работает.

Тест источника данных CSV: @CsvSource

Через @CsvSource вы можете вводить набор данных в указанном формате CSV (значения, разделенные запятыми) и использовать каждое значение, разделенное запятыми, для соответствия параметру, соответствующему методу тестирования. Ниже приведен пример использования:

@ParameterizedTest
@CsvSource({"1,One", "2,Two", "3,Three"})
void testDataFromCsv(long id, String name) {
	System.out.printf("id: %d, name: %s", id, name);
}

Текущий результат показан на рисунке.В дополнение к разделению параметров запятыми, @CsvSource также поддерживает пользовательские символы, если вы изменяете егоdelimiterОК, по умолчанию.

JUnit также предоставляет способ чтения данных файла внешнего формата CSV в качестве реализации источника данных.Нам нужно только указать путь к файлу ресурсов с помощью @CsvFileSource.Это так же просто, как и @CsvSource, и здесь повторяться не будем.

Путь к файлу ресурсов, указанный @CsvFileSource, должен начинаться с/Для начала найдите файлы в текущем каталоге тестовых ресурсов.

В дополнение к трем методам источника данных, упомянутым выше, JUnit также предоставляет следующие три источника данных:

  • @EnumSource: позволяет нам передать значение параметра в указанный тип перечисления Enum для создания определенного значения в типе перечисления.
  • @MethodSource: укажите метод, который возвращает объект Stream/Array/Iterable в качестве источника данных. Обратите внимание, что метод должен быть статическим и не может принимать никаких параметров.
  • @ArgumentSource: использовать класс параметров, реализующий интерфейс ArgumentsProvider, в качестве источника данных и переопределить его.provideArgumentsМетод может возвращать Stream пользовательского типа, который можно использовать в качестве данных, требуемых тестовым методом.

Учащиеся, которым интересны приведенные выше три аннотации к источникам данных, могут обратиться к классу ParameterizedUnitTest примера проекта, который не будет вводиться здесь снова и снова.

Эпилог

На этом этапе вы должны иметь базовое понимание и мастерство JUnit 5. Говорят, что модульное тестирование является важным звеном для улучшения качества программного обеспечения и повышения эффективности исследований и разработок.Начните с написания модульных тестов с помощью JUnit 5 и выработайте привычку писать тестовый код.Повышайте эффективность собственной разработки на постоянной практике, чтобы код, который вы пишете, имел лучшую гарантию качества.

Рекомендуемое чтение

использованная литература