Рубрики
Без рубрики

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

Привет Dev.to ! В этом посте я хотел бы сосредоточиться на модульном тестировании на Java с помощью библиотеки JUnit 5. Оно… С пометкой java, новички, тестирование.

Привет Dev.to ! В этом посте я хотел бы сосредоточиться на модульном тестировании на Java с помощью библиотеки JUnit 5. В нем представлены новые функции и подходы к тестированию по сравнению со старой версией JUnit4, которые стоит проверить. Мы рассмотрим, что такое модульный тест и зачем его тестировать; как установить JUnit 5 в ваш проект; что такое базовая структура тестирования; как использовать API утверждений и как объединить несколько тестов в наборе тестов.

Кстати, оригинальный пост был опубликован в моем блоге, и вы можете найти его здесь

Что такое модульное тестирование?

Модульное тестирование – это уровень тестирования программного обеспечения, когда мы тестируем отдельные компоненты программного обеспечения изолированно. Например, у нас есть Пользовательский сервис . Он может иметь различные связанные зависимости, такие как UserDao для подключения к источнику данных или Поставщик электронной почты для отправки электронных писем с подтверждением. Но для модульного тестирования мы изолируем Пользовательский сервис и может имитировать связанные зависимости (как это сделать, мы увидим в следующей главе).

Модульное тестирование предлагает нам ряд преимуществ, и это лишь некоторые из них:

  • Это повышает нашу уверенность, когда мы меняем код. Если модульные тесты написаны хорошо и если они выполняются каждый раз при изменении любого кода, мы можем заметить любые сбои, когда вводим новые функции
  • Он служит документацией. Конечно, документирование вашего кода включает в себя несколько инструментов, и модульное тестирование является одним из них – оно описывает ожидаемое поведение вашего кода для других разработчиков.
  • Это делает ваш код более многоразовым , что касается хороших модульных тестов, компоненты кода должны быть модульными.

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

Установить JUnit 5

Поддержка инструментов сборки

Для встроенной поддержки JUnit 5 у вас должна быть версия Gradle 4.6+ или Maven 2.22.0+.

Для Maven вам нужно добавить в свой Pom.xml :


    org.junit.jupiter
    junit-jupiter-api
    {$version}
    test

Для Gradle добавьте в build.gradle :

testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '$version'

Вы можете найти последнюю версию JUnit 5 в официальный репозиторий . Кстати, если вы хотите использовать JUnit 5 с Vert.x framework, есть расширение Vert.x JUnit5/| .

Поддержка IDE

Intellij IDEA поддерживает JUnit5 изначально с 2016.2, а Eclipse – с 4.7.1a.

Анатомия модульного теста

Базовая структура

Рассмотрим этот пример: у нас есть программа, которая выполняет линейный поиск числа в массиве целых чисел. Вот основной класс, который мы помещаем в папку src/main/java/ :

class LinearSearcher(){

    private int[] data;

   LinearSearcher(int[] arr){
      this.data = arr;
   }

   int getPositionOfNumber(int value){
      int n = data.length; 
      for(int p = 0; i < n; i++) 
         { 
           if(data[p] == value) 
            return p; 
          } 
      return -1; 
   }
}

А затем добавьте этот второй код в папку src/test/java :

class LinearSearcherTest{

    private static LinearSearcher searcher;

    //before
    @BeforeAll
    static void setup(){
       int[] array = {2, 3, 4, 10, 40};
       searcher = new LinearSearcher(array);
    }

    //Actual test methods
    @Test
    void getPosition(){
       int result = searcher.getPositionOfNumber(10);
       Assertions.assertEquals(3,result);
    }

    @Test
    void noSuchNumber(){
       int result = searcher.getPositionOfNumber(55);
       Assertions.assertEquals(-1, result);
    }

    //after
    @AfterAll
    static void finish(){
       System.out.println("Tests are finished!");
    }
}

Давайте проверим, что мы сделали в этом коде. Мы ввели новый класс, LinearSearcher , который имеет один метод – get Position Of Number , который возвращает позицию значения в массиве или возвращает -1, если значение не представлено в массиве.

Во втором классе, Linearsearchtest мы на самом деле проводим модульное тестирование. Мы ожидаем два сценария: когда у нас есть число в массиве (в нашем случае 10), мы ожидаем получить его позицию (3). Если такое число не представлено (например, 55), наш поисковик должен вернуть значение -1. Теперь вы можете запустить этот код и проверить результаты.

Предшествующие методы

Вы могли бы отметить два метода, аннотированных соответственно с помощью @beforeAll и @в конце концов . Что они делают? Первый метод соответствует Перед методами . Их здесь двое:

  • @beforeAll – статический метод, который будет выполняться один раз до все @test метод в текущем классе.
  • @beforeEach – метод, который будет выполняться перед каждым @test метод в текущем классе.

Эти методы удобны для настройки среды модульного тестирования (например, для создания экземпляров).

После методов

Поскольку есть перед методами , есть После методов . Есть также пара из них:

  • @AfterAll – метод static будет выполнен один раз после все @test методы в текущем классе.
  • @afterEach – метод, который будет выполняться после каждого @test метод в текущем классе.

Использование стандартного API утверждений

Assertions API – это набор служебных методов, которые поддерживают утверждение условий в тестах. Существует множество доступных методов, однако мы бы остановились на наиболее важных из них.

Утверждать, что не нулевой

Когда нам нужно утверждать, что фактический объект не равен null, мы можем использовать этот метод:

assertNotNull(Object obj);

Если object не равен null, метод проходит, если нет – завершается с ошибкой.

УтвержДающие значения

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

assertEquals(expected_value, actual_value, optional_message);

Эти методы имеют два обязательных аргумента и один необязательный аргумент :

  • результат expected_value, мы хотим получить
  • фактическое значение проверенное значение
  • сообщение optional_, которое будет отображаться в STDOUT в случае сбоя метода.

Значения могут быть примитивных типов: int, double, float, long, short, boolean, char, byte, а также строк и объектов. К этой группе мы можем добавить следующие методы тестирования:

  • assertArrayEquals – проверяет, что ожидаемые и фактические массивы равны. Массивы имеют примитивные типы
  • assertFalse и assertTrue – проверьте, что заданное логическое условие равно false или true соответственно
  • assert Iterable Равно – то же, что и assertArrayEquals, но для Iterables ( например, Список, набор и т.д.)

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

Утверждать, что бросает

Это нововведение JUnit 5. Учтите, что у вас есть метод, который выдает исключение:

Car findCarById(String id) throws FailedProviderException;

Этот метод извлекает человека Car из базовой базы данных по ее идентификатору и выдает ошибку ProviderException при возникновении проблемы с базой данных. Другими словами, мы заключаем в интерфейс возможные исключения источника данных (например, SQLException или исключения для баз данных NoSQL) и достигаем его независимости от реализации.

Как мы можем проверить, что генерируется исключение? Раньше, в JUnit4, мы использовали аннотации:

@Test(expected = FailedProviderException.class)
void exceptionThrownTest() throws Exception{
    Car result = repository.findCarById("non-existing-id");
}

Кстати, та же идея используется и при тестировании. В JUnit 5 был введен метод assertThrows . Взгляните, как бы мы справились с такой же ситуацией:

@Test
void exceptionThrownTest(){
    Assertions.assertThrows(FailedProviderException.class, ()->{
        Car result = repository.findCarById("non-existing-id");
    });
}

Сигнатура этого метода состоит из двух компонентов:

  1. Ожидаемое исключение, которое будет выдано
  2. Лямбда-выражение Исполняемого файла , который содержит фрагмент кода, который потенциально вызывает исключение.

Опять же, как мы уже упоминали методы группы assertequals, мы можем предоставить необязательное сообщение String в качестве третьего аргумента.

Тайм-аут утверждения

Когда нам нужно подтвердить, что тест завершен в течение определенного времени ожидания, мы можем использовать этот метод:

assertTimeout(Duration timeout, Executable executable)

Идея та же, что и в методе assert Throws, но там мы указываем timeout . Второй аргумент – это то же самое исполняемое лямбда-выражение. Третий необязательный компонент – это строковое сообщение. Давайте рассмотрим пример:

@Test
void in3secondsTest(){
   Assertions.assertTimeout(Duration.ofSeconds(3), ()->{
      //some code
   });
}

Пожалуйста, обратите внимание, что этот метод использует Duration API для указания таймфрейма. В нем есть несколько удобных методов, таких как of Seconds(), ofMills() и т.д. Если вы с ним не знакомы, не стесняйтесь ознакомиться с этим руководством .

Провал

Наконец, что, если нам нужно провалить тест? Просто используйте метод Assertions.fail() . Опять же, их несколько:

  • сбой (Строка теста с заданным сообщением об ошибке.
  • ошибка (строковое сообщение, вызывающее тест с заданным сообщением об ошибке, а также основной причиной.
  • сбой (Выбрасываемый) теста с заданной основной причиной.

Создание наборов тестов

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

Такой подход позволяет запускать тесты, распределенные по нескольким тестовым классам и различным пакетам.

Предположим, что у нас есть тесты TESTA, TestB, testC, которые разделены на три пакета: net.mednikov.tests tutorial.GroupA, net.mednikov.teststutorial.GroupA, net.mednikov.teststutorial.groupC соответственно. Мы можем написать набор тестов, чтобы объединить их:

@RunWith(JUnitPlatform.class)
@SelectPackages({net.mednikov.teststutorial.groupA, net.mednikov.teststutorial.groupB, net.mednikov.teststutorial.groupC})
public class TestSuite(){}

Теперь вы можете запустить этот метод как один набор тестов.

Рекомендации

  • Серджио Мартин. Выведите модульное тестирование на новый уровень С помощью JUnit 5 (2018). Зона, читайте здесь
  • Петри Кайнулайнен. Написание утверждений с помощью JUnit 5 Assertion API (2018), читайте здесь
  • Джей Стивен Перри. API JUnit 5 Jupiter/| (2017) Разработчик IBM, читайте здесь

Вывод

В этом посте мы узнали, что такое модульное тестирование и зачем его проводить; как установить JUnit 5 в свой проект; что такое базовая структура тестирования; как использовать API утверждений и как объединить несколько тестов из разных пакетов в набор тестов. Но, конечно, JUnit 5 – огромная тема, и этот пост – всего лишь вершина айсберга. Некоторые фреймворки, такие как Vert.x, предлагают специальные расширения JUnit 5, такие как vertx-junit 5 . Удачи с JUnit 5 !:)

Оригинал: “https://dev.to/iuriimednikov/how-to-start-with-unit-testing-in-java-a-complete-introduction-to-junit-5-3cc”