Платформы тестирования Java: выбор подходящего инструмента для работы — JUnit, AssertJ и Hamcrest

Платформы тестирования Java: выбор подходящего инструмента для работы — JUnit, AssertJ и Hamcrest

9 января 2024 г.

Тестирование — один из наиболее важных аспектов при создании приложений. В Java имеется несколько сред тестирования, которые помогают тестировать различные функции. Но сначала нам нужно хорошо понять, зачем мы пишем тесты.

Почему тесты?

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

Типы тестов

При написании тестов важно иметь представление о типах тестов, связанных с тестированием. В этом разделе мы рассмотрим наиболее распространенные типы тестов и поймем, что они означают.

  • Модульные тесты/Модульное тестирование → Это тесты, написанные для проверки определенных разделов вашего кода. Они не имеют внешних зависимостей и предназначены для очень быстрой работы.
  • Интеграционные тесты / Интеграционное тестирование → Это тесты, написанные для проверки поведения между объектами и частями общей системы. Обычно они пишутся для более широкой области применения и работают медленнее, чем модульные тесты.
  • Функциональные тесты / Функциональное тестирование → Эти тесты предназначены для тестирования работающего приложения. Обычно в ходе этих тестов проверяются функциональные точки взаимодействия (например, вызов веб-сервисов, отправка/получение сообщений и т. д.).

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

Юнит

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

  • Платформа JUnit → Основа для запуска фреймворков тестирования на JVM. Это позволяет запускать тесты из панели запуска консоли или создавать такие инструменты, как Maven и Gradle.
  • JUnit Jupiter → Модель программирования от написания тестов и расширений для JUnit.
  • JUnit Vintage → Предоставляет тестовый механизм для запуска тестов JUnit 3 и JUnit 4.

При использовании JUnit мы связываемся с довольно большим количеством аннотаций. Некоторые из наиболее распространенных аннотаций JUnit показаны ниже.

  • @Test → Отмечает метод как тестовый.
  • @ParameterizedTest → Отмечает метод как параметризованный тест.
  • @RepeatedTest → Повторить тест N раз.
  • @TestFactory → Фабричный метод тестирования для динамических тестов.
  • @TestInstance → Используется для настройки жизненного цикла тестового экземпляра.
  • @TestTemplate → Создает шаблон, который будет использоваться в нескольких тестовых примерах.
  • @DisplayName → Понятное имя для теста.
  • @BeforeEach → Метод, запускаемый перед каждым тестовым примером.
  • @AfterEach → Метод, запускаемый после каждого тестового примера.
  • @BeforeAll → Статический метод, запускаемый перед всеми тестовыми примерами в текущем классе.
  • @AfterAll → Статический метод, запускаемый после всех тестовых случаев в текущем классе.
  • @Nested → Создает вложенный тестовый класс.
  • @Tag → Объявляет «теги» для тестов фильтрации.
  • @Disabled → Отключить тест или тестовый класс.
  • @ExtendWith → Используется для регистрации расширений.

Мы будем внимательно учитывать это при разработке проекта.

Загрязнение рук с JUnit

Давайте создадим новый проект Maven, чтобы протестировать вышеупомянутые аннотации и получить хорошее представление о JUnit. В этой статье мы не будем рассматривать, как установить Java или Maven в вашу систему. Чтобы понять, как можно легко настроить Java и Maven, вы можете обратиться к этому статья. В этой статье мы будем использовать IntelliJ IDEA в качестве нашей IDE. Если вы не знакомы с IntelliJ IDEA, вы можете использовать для этого любую IDE по своему усмотрению.

Часть 1. Настройка проекта

Перейдите в IntelliJ IDEA → Файл → Создать → Проект, чтобы создать новый проект. Поскольку это простой проект, я выбрал следующие свойства, как показано на изображении.

1.1 Project Setup

Хотя я создал проект для Java 16 (corretto-16), мне нужно использовать Java 17 в моем проекте. Так как я могу это исправить? Чтобы использовать Java 17 в своем проекте, просто перейдите в Файл → Структура проекта и выберите там Java 17. Однако учтите, что для использования Java 17 на вашем компьютере должен быть установлен Java 17 SDK.

1.2 Change Java Version

Но просто изменить это не получится, поскольку вам также придется обновить версию Java в файле pom.xml. Чтобы обновить версию Java, перейдите в pom.xml и измените свойства, как показано ниже.

<properties>
  <maven.compiler.source>17</maven.compiler.source>
  <maven.compiler.target>17</maven.compiler.target>
  ...
</properties> 

Теперь давайте добавим JUnit в наш проект, добавив зависимость JUnit в файл pom.xml. Сначала определите версию JUnit, которую мы используем, в разделе properties, как показано ниже.

<properties>
  ...
  <junit-platform.version>5.9.2</junit-platform.version>
</properties>

После этого определите зависимости, добавив следующее в файл pom.xml.

<dependencies>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>${junit-platform.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>${junit-platform.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

После этого добавьте плагины сборки, добавив следующее в файл pom.xml.

<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.11.0</version>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>3.1.2</version>
    </plugin>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-failsafe-plugin</artifactId>
      <version>2.22.0</version>
    </plugin>
  </plugins>
</build>

После этого вы можете синхронизировать настройки Maven из IDE и проверить, правильно ли работает проект, дважды щелкнув зависимость жизненного цикла test (в качестве альтернативы вы можете запустить mvn clean test). и в вашем терминале).

1.3 Dependencies added

Поскольку мы создаем небольшой проект, мы создадим новый файл в org.nipunaupeksha.junit5 с именем SimpleCalculator, в котором будут записаны основные арифметические операции (+,-,/,*). там.

package org.nipunaupeksha;

public class SimpleCalculator {
    public double add(double numberA, double numberB) {
        return numberA + numberB;
    }

    public double subtract(double numberA, double numberB) {
        return numberA - numberB;
    }

    public double divide(double numberA, double numberB) {
        return numberA / numberB;
    }

    public double multiply(double numberA, double numberB) {
        return numberA * numberB;
    }

}

После этого вы можете протестировать эту простую программу, написав приведенный ниже код в файле org.nipunaupeksha.junit5 → Main.java.

package org.nipunaupeksha;

public class Main {
    public static void main(String[] args) {

        SimpleCalculator simpleCalculator = new SimpleCalculator();

        double resAdd = simpleCalculator.add(5, 10);
        double resSubtract = simpleCalculator.subtract(15, 10);
        double resDivide = simpleCalculator.divide(10, 3);
        double resMultiply = simpleCalculator.multiply(5, 2);

        System.out.println("Addition result: " + resAdd);
        System.out.println("Subtraction result: " + resSubtract);
        System.out.println("Division result: " + resDivide);
        System.out.println("Multiplication result: " + resMultiply);
    }
}

Вывод приведенного выше фрагмента кода показан ниже.

1.4 SimpleCalculator output

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

Часть 2. Написание простого теста

При написании тестовых примеров важно соблюдать правильную структуру файлов. Лучше всего использовать ту же структуру каталогов, которую вы используете для раздела main кода для раздела test. Поэтому сначала создайте пакет с именем org.nipunaupeksha внутри раздела test.

Затем создайте файл с именем SimpleCalculatorTest внутри этого пакета.

2.1 Create a new test file named SimpleCalculatorTest

Вместо того, чтобы создавать новый пакет и файл, вы также можете легко сделать это с помощью IntelliJ IDEA. Чтобы создать новый тестовый файл, просто щелкните класс, который вы хотите протестировать (в нашем случае SimpleCalculator) и нажмите Option + Enter на Mac (в Linux и Windows это Параметр может измениться в соответствии с привязками клавиш (Keymap), которые вы установили в IntelliJ IDEA). Затем появится несколько опций, и из этих опций вы можете выбрать Создать тест, чтобы создать новый тестовый файл.

Теперь перейдите к файлу SimpleCalculatorTest и создайте свой первый тест, добавив следующий код.

package org.nipunaupeksha;

import org.junit.jupiter.api.Test;

public class SimpleCalculatorTest {

    @Test
    void testAdd() {
    }
}

Как видите, я использовал аннотацию @Test в приведенном выше фрагменте кода. Чтобы показать приложению, что созданный вами метод предназначен только для целей тестирования, вам необходимо пометить его аннотацией @Test. Поскольку мы не писали никакого кода внутри метода, testAdd, если бы мы запустили этот тест, он прошел бы без каких-либо ошибок. (Если у вас возникают какие-либо ошибки из-за версии Java, исправьте это в настройках проекта).

2.2 Test passing without any errors

Теперь, если мы хотим протестировать какой-либо код, который мы написали в классе SimpleCalculator, нам нужно перенести этот объект в файл SimpleCalculatorTest. Но проблема в том, что для каждого теста нам нужно создавать новый объект SimpleCalculator. Как же это исправить?

Поскольку нам нужно создавать новый объект SimpleCalculator каждый раз, когда мы запускаем тестовый метод (например, testAdd, testSubtract (еще не реализовано)) мы допустим две ошибки, если собираемся напрямую создать этот объект, как показано ниже.

...
@Test
void testAdd(){
  SimpleCalculator simpleCalulator = new SimpleCalculator();
  ...
}

@Test
void testAdd(){
  SimpleCalculator simpleCalulator = new SimpleCalculator();
  ...
}
...

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

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
    }
}

Подобно @BeforeEach мы можем использовать аннотацию @AfterEach, чтобы удалить любой из созданных нами объектов или созданных нами конфигураций. Обычно имена методов setUp и tearDown используются для аннотаций @BeforeEach и @AfterEach соответственно.

Подобно аннотациям @BeforeEach и @AfterEach, вы можете использовать аннотации @BeforeAll и @AfterAll. Но обратите внимание, что эти аннотации можно использовать только со статическими методами. Поэтому любые конфигурации или объекты, которые вы создаете, выполняются в статическом контексте.

Часть 3. Утверждения JUnit5

При использовании JUnit большую часть времени приходится иметь дело с утверждениями. Утверждения просто означают, что вы утверждаете условие прохождения теста. Допустим, add(double NumberA, Double NumberB) возвращает значение, которое мы добавили в переменную с именем result, и мы передаем числа 2, 4 в add(). Тогда результат, который мы должны получить, должен быть равен 6. Мы можем утверждать, что метод работает правильно с утверждениями, предоставляемыми JUnit (например, assertEquals).

Давайте напишем утверждение, чтобы проверить, верен или нет вывод, который мы получаем от метода add().

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        assertEquals(6, simpleCalculator.add(2, 4));
    }
}

Как видите, мы использовали статический метод assertEquals(), чтобы подтвердить правильность вывода, который мы получаем от метода add(). Теперь мы можем запустить его, щелкнув значок желоба.

3.1 Test addTest running without any errors

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

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

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

3.2 Assertions with fail message

Как вы можете видеть, поскольку утверждение не удалось, оно выдает сообщение Возвращён неверный результат. Теперь мы можем проверить переданные параметры и определить, что мы передали 2 и 4 в add. () вместо 2 и 3. Чтобы повысить производительность сообщения, если его создание требует больших затрат, вы можете использовать лямбда-выражения, чтобы гарантировать, что сообщение о сбое создается только в том случае, если утверждение не удалось.

3.3 Assertions with a fail message (using lambda methods)

Часть 4. Сгруппированные утверждения

Мы также можем создавать сгруппированные утверждения с помощью JUnit. Например, предположим, что нам нужно подтвердить все методы расчета за один раз. Мы можем использовать сгруппированные утверждения, как показано ниже.

 package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 4), () -> "Wrong result returned.");
    }

    @Test
    void testCalculations(){
        assertAll("Calculations Test",
                ()-> assertEquals(5, simpleCalculator.add(2,3)),
                ()-> assertEquals(1, simpleCalculator.subtract(2,1)),
                ()-> assertEquals(4, simpleCalculator.divide(8,2)),
                ()-> assertEquals(10, simpleCalculator.multiply(2,5)));
    }
}

Первый параметр assertAll — это заголовок вашей группы утверждений, а соответствующие свойства — это то, что вы можете протестировать как утверждения. Теперь, если мы запустим этот тест, используя значок желоба, мы увидим, что все они работают правильно.

4.1 Grouped Assertions without any errors

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

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 4), () -> "Wrong result returned.");
    }

    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5)), "Multiplication failed.");
    }
}

Часть 5. Пропуск тестов JUnit

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

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Disabled
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

Теперь, если мы запустим все тесты, доступные в классе SimpleCalculatorTest, он проверит только метод testAdd.

5.1 Skipping JUnit tests

Аналогичным образом вы можете отключить все методы тестирования в определенном классе, добавив для класса аннотацию @Disabled. Например, если мы хотим отключить все методы тестирования в классе SimpleCalculatorTest, мы можем использовать следующий фрагмент кода.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

@Disabled
public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

И мы также можем добавить сообщение в аннотацию @Disabled.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

@Disabled(value = "Testing @Disabled Annotation")
public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

Часть 6. Присвоение названий тестам

Когда мы пишем и тестируем тесты, мы можем видеть только имя тестового метода, например, testAdd или testCalculations. Но если мы будем использовать для наших тестов более понятные имена, это поможет другим разработчикам понять написанные нами тесты, а также улучшить качество нашего кода. Для этого мы можем использовать аннотацию @DisplayName.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @DisplayName("Testing the add() method in SimpleCalculator")
    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Disabled
    @DisplayName("Testing all the arithmetic operations in SimpleCalculator")
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

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

6.1 Display names of the tests

Часть 7. Проверка ожидаемых исключений

Мы знаем, что если вы не можете разделить любое число на 0. Поэтому нам нужно немного изменить наш код SimpleCalculator, чтобы он вызывал ArithmeticException, когда numberB равен 0. Давайте сначала сделаем это.

package org.nipunaupeksha;

public class SimpleCalculator {
    public double add(double numberA, double numberB) {
        return numberA + numberB;
    }

    public double subtract(double numberA, double numberB) {
        return numberA - numberB;
    }

    public double divide(double numberA, double numberB) {
        if(numberB == 0) throw new ArithmeticException("numberB cannot be zero");
        return numberA / numberB;
    }

    public double multiply(double numberA, double numberB) {
        return numberA * numberB;
    }

}

Теперь мы изменили наш код. Как мы можем это проверить? Тестирование ожидаемых исключений в JUnit сильно отличается от простых утверждений, поскольку для этого нам нужно использовать assertThrows. Давайте создадим новый тест с именем testDivideException для этого сценария, чтобы проверить, генерируется ли ArithmeticException.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @DisplayName("Testing the add() method in SimpleCalculator")
    @Test
    void testAdd() {
        // assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Test
    void testDivideException() {
        assertThrows(ArithmeticException.class, () -> simpleCalculator.divide(10, 0));
    }

    @Disabled
    @DisplayName("Testing all the arithmetic operations in SimpleCalculator")
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

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

7.1 Testing expected exceptions

Часть 8. Предположения JUnit

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

...
@Test
void testAddAssumption(){
  assumeTrue(simpleCalculator.add(2,4) == 5);
}
...

Теперь вы можете видеть, что сделанное нами предположение неверно, поскольку 2+4 не равно 5. Если мы запустим это, наш тест testAddAssumption не завершится неудачей, но будет проигнорирован.

8.1 JUnit Assumptions

Допущения действительно важны, когда нет смысла продолжать выполнение данного метода тестирования — например, если тест зависит от чего-то, чего не существует в текущей среде выполнения.

Часть 9. Выполнение условного теста с помощью JUnit

Мы также можем выполнить условное тестирование с помощью JUnit. Предположим, у вас есть метод, который вам нужно протестировать на Mac и Windows. Для этого вы можете использовать условное тестирование. Аналогично, если вы хотите протестировать свой метод с различными версиями Java, вы также можете сделать это с помощью условного тестирования. Ниже приведены несколько примеров использования условного тестирования. Дополнительную информацию об условном тестировании можно найти здесь.

...
@DisplayName("Testing the add() method in MacOs")
@EnabledOnOs(OS.MAC)
@Test
void testAddOnMacOs(){
  assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
}

@DisplayName("Testing the add() method in Windows")
@EnabledOnOs(OS.WINDOWS)
@Test
void testAddOnWindows(){
  assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
}

@DisplayName("Testing the add() method in Java 8")
@EnabledOnJre(JRE.JAVA_8)
@Test
void testAddOnJava8(){
  assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
}

@DisplayName("Testing the add() method in Java 17")
@EnabledOnJre(JRE.JAVA_17)
@Test
void testAddOnJava17(){
  assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
}
...

Теперь, если мы запустим тестовый класс SimpleCalculatorTest, вы увидите, что он не запускает testAddOnWindows и testAddOnJava8, поскольку я использую MacOS и Java 17 в этом проекте.

9.1 Conditional Testing

В дополнение к вышеупомянутым аннотациям вы можете использовать аннотацию @EnabledIfEnvironmentVariable (например, @EnabledIfEnvironmentVariable(named=”USER”, match=”nipunaupeksha”)) для условного запуска ваших тестов. на основе переменных вашей среды.

Часть 10. Использование AssertJ с JUnit

AssertJ — это альтернативная библиотека утверждений, которую можно использовать вместе с JUnit. В этом разделе мы рассмотрим некоторые возможности AssertJ, которые вы можете использовать с JUnit. Если вам нужна дополнительная информация об AssertJ, вы всегда можете использовать официальную документацию, чтобы ознакомиться с ней.

Чтобы использовать AssertJ, нам нужно добавить его в список зависимостей файла pom.xml.

...
<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-core</artifactId>
  <version>3.24.2</version>
  <scope>test</scope>
</dependency>
...

Теперь давайте попробуем использовать AssertJ для написания нового тестового примера для нашего метода subtract() в SimpleCalculator.

import static org.assertj.core.api.Assertions.assertThat;
...
@DisplayName("Testing the subtract() method in SimpleCalculator using AssertJ")
@Test
void testSubtract(){
  assertThat(simpleCalculator.subtract(10, 5)).isEqualTo(5);
}
...

Как видите, простой метод assertThat предоставляет несколько методов, которые мы можем объединить для использования с утверждениями. Это упрощает код, поскольку мы импортируем только один статический метод и можем связать любое необходимое нам утверждение с этим методом assertThat (например, isEqualTo(), isNotEqualTo()).

10.1 Using AssertJ for Assertions

Часть 11. Использование Hamcrest с JUnit

Hamcrest — еще одна библиотека, которую можно использовать с JUnit для более универсальных утверждений. Давайте проверим, как можно использовать Hamcrest с JUnit, добавив его в список зависимостей файла pom.xml.

...
<dependency>
  <groupId>org.hamcrest</groupId>
  <artifactId>hamcrest-library</artifactId>
  <version>2.2</version>
  <scope>test</scope>
</dependency>
...

Давайте проверим метод multiply() SimpleCalculator с помощью Hamcrest. Поскольку нам также необходимо использовать assertThat() в Hamcrest, закомментируйте импорт AssertJ и метод тестирования testSubtract() .

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
...
@DisplayName("Testing the multiply() method in SimpleCalculator using Hamcrest")
@Test
void testMultiply() {
  assertThat(simpleCalculator.multiply(2,5), is(10.0));
}
...

11.1 Using Hamcrest Assertions

Часть 12. Теги JUnit

Теги JUnit позволяют идентифицировать тесты и объединять их в группы. Вы можете определить тесты на уровне класса или метода. В зависимости от тестовых файлов и методов вы можете правильно сгруппировать их с помощью тегов уровня класса или метода. Давайте воспользуемся тегами, чтобы сгруппировать наши тесты в файле SimpleCalculatorTest.

package org.nipunaupeksha;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;

//import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

public class SimpleCalculatorTest {

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator")
    @Test
    void testAdd() {
//        assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator as an assumption ")
    @Test
    void testAddAssumption(){
        assumeTrue(simpleCalculator.add(2,4) == 5);
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in MacOs")
    @EnabledOnOs(OS.MAC)
    @Test
    void testAddOnMacOs(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Windows")
    @EnabledOnOs(OS.WINDOWS)
    @Test
    void testAddOnWindows(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 8")
    @EnabledOnJre(JRE.JAVA_8)
    @Test
    void testAddOnJava8(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 17")
    @EnabledOnJre(JRE.JAVA_17)
    @Test
    void testAddOnJava17(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

//    @DisplayName("Testing the subtract() method in SimpleCalculator using AssertJ")
//    @Test
//    void testSubtract(){
//        assertThat(simpleCalculator.subtract(10, 5)).isEqualTo(5);
//    }

    @Tag("Division")
    @DisplayName("Testing the ArithmeticException when you divide any number by zero")
    @Test
    void testDivideException() {
        assertThrows(ArithmeticException.class, () -> simpleCalculator.divide(10, 0));
    }

    @Tag("Multiplication")
    @DisplayName("Testing the multiply() method in SimpleCalculator using Hamcrest")
    @Test
    void testMultiply() {
        assertThat(simpleCalculator.multiply(2,5), is(10.0));
    }

    @Tag("All")
    @Disabled
    @DisplayName("Testing all the arithmetic operations in SimpleCalculator")
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

Итак, теперь, если мы хотим запускать только тесты Addition, мы можем настроить его в IntelliJ IDEA, перейдя на верхнюю панель инструментов и нажав Редактировать конфигурации .

12.1 Setting up tag configurations

Нажмите на символ +, чтобы добавить новую конфигурацию JUnit и дать ей имя. Затем выберите ресурс Tags и укажите там имя тега (в нашем случае Addition).

12.2 JUnit Configurations for the tag, Addition

Теперь, если вы запустите эту конфигурацию, будут выполняться только тесты, отмеченные @Tag(“Addition”).

12.3 Running only the tests marked with the Addition tag

Часть 13. Вложенные тесты

Вы можете писать вложенные тесты, определив другой класс внутри тестового класса. Давайте создадим новый файл с именем SimpleCalculatorNestedTest для проверки вложенных тестов.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DisplayName("SimpleCalculator")
public class SimpleCalculatorNestedTest {
    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp(){
        simpleCalculator = new SimpleCalculator();
    }

    @DisplayName("Addition")
    @Nested
    class NestedAddition{

        @DisplayName("Check adding two numbers is correct.")
        @Test
        void testAddTwoNumbers(){
            assertEquals(10, simpleCalculator.add(8,2));
        }
    }

    @DisplayName("Division")
    @Nested
    class NestedDivision{

        @DisplayName("Check dividing two numbers is correct.")
        @Test
        void testAddTwoNumbers(){
            assertEquals(4, simpleCalculator.divide(8,2));
        }
    }
}

Теперь, если вы запустите это, вы увидите, что результаты теста сгруппированы из-за использования аннотации @Nested.

13.1 Nested Annotation Usage

Часть 14. Тестовые интерфейсы JUnit

Поскольку у нас есть два тестовых файла с именами SimpleCalculatorTest и SimpleCalculatorNestedTest, мы можем использовать аннотацию @Tag, чтобы сгруппировать их во что-то вроде SimpleCalculator(@Tag(“SimpleCalculator”)). Кроме того, для этого мы также можем использовать тестовые интерфейсы. Давайте создадим новый интерфейс ISimpleCalculator с тегом SimpleCalculator и будем использовать его в вышеупомянутых классах, чтобы их можно было использовать с тегом SimpleCalculator. >.

package org.nipunaupeksha;

import org.junit.jupiter.api.Tag;

@Tag("SimpleCalculator")
public interface ISimpleCalculator {
}

package org.nipunaupeksha;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;

//import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

public class SimpleCalculatorTest implements ISimpleCalculator{

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator")
    @Test
    void testAdd() {
//        assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator as an assumption ")
    @Test
    void testAddAssumption(){
        assumeTrue(simpleCalculator.add(2,4) == 5);
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in MacOs")
    @EnabledOnOs(OS.MAC)
    @Test
    void testAddOnMacOs(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Windows")
    @EnabledOnOs(OS.WINDOWS)
    @Test
    void testAddOnWindows(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 8")
    @EnabledOnJre(JRE.JAVA_8)
    @Test
    void testAddOnJava8(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 17")
    @EnabledOnJre(JRE.JAVA_17)
    @Test
    void testAddOnJava17(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

//    @DisplayName("Testing the subtract() method in SimpleCalculator using AssertJ")
//    @Test
//    void testSubtract(){
//        assertThat(simpleCalculator.subtract(10, 5)).isEqualTo(5);
//    }

    @Tag("Division")
    @DisplayName("Testing the ArithmeticException when you divide any number by zero")
    @Test
    void testDivideException() {
        assertThrows(ArithmeticException.class, () -> simpleCalculator.divide(10, 0));
    }

    @Tag("Multiplication")
    @DisplayName("Testing the multiply() method in SimpleCalculator using Hamcrest")
    @Test
    void testMultiply() {
        assertThat(simpleCalculator.multiply(2,5), is(10.0));
    }

    @Tag("All")
    @Disabled
    @DisplayName("Testing all the arithmetic operations in SimpleCalculator")
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DisplayName("SimpleCalculator")
public class SimpleCalculatorNestedTest implements ISimpleCalculator{
    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp(){
        simpleCalculator = new SimpleCalculator();
    }

    @DisplayName("Addition")
    @Nested
    class NestedAddition{

        @DisplayName("Check adding two numbers is correct.")
        @Test
        void testAddTwoNumbers(){
            assertEquals(10, simpleCalculator.add(8,2));
        }
    }

    @DisplayName("Division")
    @Nested
    class NestedDivision{

        @DisplayName("Check dividing two numbers is correct.")
        @Test
        void testAddTwoNumbers(){
            assertEquals(4, simpleCalculator.divide(8,2));
        }
    }
}

Теперь, если мы хотим создать конфигурацию JUnit для выполнения тега SimpleCalculator, он будет выполнять файлы SimpleCalculatorTest и SimpleCalculatorNestedTest. Кроме того, мы также можем определить методы по умолчанию в интерфейсе. Например, если мы хотим вывести что-то вроде Выполняются тесты SimpleCalculator, мы можем реализовать это, как показано ниже. Мы можем использовать это более широко, чтобы избежать повторения кода.

package org.nipunaupeksha;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Tag("SimpleCalculator")
public interface ISimpleCalculator {
    @BeforeAll
    default void beforeAll(){
        System.out.println("SimpleCalculator tests are running");
    }
}

Часть 15. Повторные тесты

Если мы хотим повторить тест несколько раз, мы можем использовать аннотацию @RepeatedTest. Здесь необходимое количество повторений передается в качестве параметра.

...
@Tag("Addition")
@DisplayName("Repeatedly testing the add() method")
@RepeatedTest(10)
void testAddRepeat(){
  assertEquals(5, simpleCalculator.add(2,3), ()-> "Wrong result returned,");
}
...

Мы можем дополнительно настроить это, изменив параметры, передаваемые в @RepeatedTest(). Например, если мне нужны отображаемые имена в виде Повторный тест: 1 из 10, я могу настроить его, как показано ниже.

...
@Tag("Addition")
@DisplayName("Repeated Test")
@RepeatedTest(value= 10, name= "{displayName}: {currentRepetition} of {totalRepetitions}")
void testAddRepeat(){
  assertEquals(5, simpleCalculator.add(2,3), ()-> "Wrong result returned,");
}
...

15.1 Repeated Testing

Часть 16. Параметризованные тесты JUnit

Чтобы использовать параметризованные тесты в JUnit, нам нужно добавить еще одну зависимость в список зависимостей в файле pom.xml. Это junit-jupiter-params . После его добавления вы можете писать параметризованные тесты с помощью JUnit.

<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-params</artifactId>
  <version>${junit-platform.version}</version>
  <scope>test</scope>
</dependency>

Давайте напишем наш первый параметризованный тест для метода multiply().

...
@ParameterizedTest
@ValueSource(doubles={5, 10})
void testMultiplyWithValueSource(double val){
  assertEquals(val, simpleCalculator.multiply(val, 1));
}
...

Теперь, если вы запустите этот тест, предоставленные значения 5,10 будут переданы в качестве аргументов этому методу testMultiplyWithValueSource

16.1 Parameterized Test with a Value Source

Если нам нужно дать понятные для пользователя имена нашим параметризованным тестам, мы можем сделать это, используя аннотацию @DisplayName с аннотацией @ParameterizedTest, как показано ниже.

...
@DisplayName("Value source multiplication test")
@ParameterizedTest(name="{displayName} - [{index}] {arguments}")
@ValueSource(doubles={5, 10})
void testMultiplyWithValueSource(double val){
  assertEquals(val, simpleCalculator.multiply(val, 1));
}
...

При этом будут использоваться заполнители, упомянутые в аннотации @ParameterizedTest, и будут предоставлены удобные для пользователя отображаемые имена.

16.2 Better display names for parameterized tests

Следует помнить одну вещь: хотя мы использовали аннотацию @ValueSource, чтобы показать пример использования параметризованных тестов, вы можете использовать такие аннотации, как @EnumSource, @CsvSource< /code> , @CsvFileSource, @MethodSource, @ArgumentsSource в зависимости от параметров, которые вы хотите передать.

Часть 17. Расширения JUnit

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

package org.nipunaupeksha.junitextensions;

import java.lang.reflect.Method;
import java.util.logging.Logger;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store;

/**
 * source -  https://github.com/junit-team/junit5/blob/main/documentation/src/test/java/example/timing/TimingExtension.java
 */

public class TimingExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback {

    private static final Logger logger = Logger.getLogger(TimingExtension.class.getName());

    private static final String START_TIME = "start time";

    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        getStore(context).put(START_TIME, System.currentTimeMillis());
    }

    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        Method testMethod = context.getRequiredTestMethod();
        long startTime = getStore(context).remove(START_TIME, long.class);
        long duration = System.currentTimeMillis() - startTime;

        logger.info(() ->
                String.format("Method [%s] took %s ms.", testMethod.getName(), duration));
    }

    private Store getStore(ExtensionContext context) {
        return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod()));
    }
}

Теперь перейдите к файлу SimpleCalculatorTest и используйте аннотацию @ExtendWith(TimingExtension.class), чтобы использовать TimingExtension с этим классом.< /п>

package org.nipunaupeksha;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.nipunaupeksha.junitextensions.TimingExtension;

//import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

@ExtendWith(TimingExtension.class)
public class SimpleCalculatorTest implements ISimpleCalculator{

    private SimpleCalculator simpleCalculator;

    @BeforeEach
    void setUp() {
        simpleCalculator = new SimpleCalculator();
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator")
    @Test
    void testAdd() {
//        assertEquals(5, simpleCalculator.add(2, 4), "Wrong result returned.");
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in SimpleCalculator as an assumption ")
    @Test
    void testAddAssumption(){
        assumeTrue(simpleCalculator.add(2,4) == 5);
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in MacOs")
    @EnabledOnOs(OS.MAC)
    @Test
    void testAddOnMacOs(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Windows")
    @EnabledOnOs(OS.WINDOWS)
    @Test
    void testAddOnWindows(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 8")
    @EnabledOnJre(JRE.JAVA_8)
    @Test
    void testAddOnJava8(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Testing the add() method in Java 17")
    @EnabledOnJre(JRE.JAVA_17)
    @Test
    void testAddOnJava17(){
        assertEquals(5, simpleCalculator.add(2, 3), () -> "Wrong result returned.");
    }

    @Tag("Addition")
    @DisplayName("Repeated Test")
    @RepeatedTest(value= 10, name= "{displayName}: {currentRepetition} of {totalRepetitions}")
    void testAddRepeat(){
        assertEquals(5, simpleCalculator.add(2,3), ()-> "Wrong result returned,");
    }

//    @DisplayName("Testing the subtract() method in SimpleCalculator using AssertJ")
//    @Test
//    void testSubtract(){
//        assertThat(simpleCalculator.subtract(10, 5)).isEqualTo(5);
//    }

    @Tag("Division")
    @DisplayName("Testing the ArithmeticException when you divide any number by zero")
    @Test
    void testDivideException() {
        assertThrows(ArithmeticException.class, () -> simpleCalculator.divide(10, 0));
    }

    @Tag("Multiplication")
    @DisplayName("Testing the multiply() method in SimpleCalculator using Hamcrest")
    @Test
    void testMultiply() {
        assertThat(simpleCalculator.multiply(2,5), is(10.0));
    }

    @DisplayName("Value source multiplication test")
    @ParameterizedTest(name="{displayName} - [{index}] {arguments}")
    @ValueSource(doubles={5, 10})
    void testMultiplyWithValueSource(double val){
        assertEquals(val, simpleCalculator.multiply(val, 1));
    }

    @Tag("All")
    @Disabled
    @DisplayName("Testing all the arithmetic operations in SimpleCalculator")
    @Test
    void testCalculations() {
        assertAll("Calculations Test",
                () -> assertEquals(5, simpleCalculator.add(2, 3), "Addition failed."),
                () -> assertEquals(1, simpleCalculator.subtract(2, 1), "Subtraction failed."),
                () -> assertEquals(4, simpleCalculator.divide(8, 2), "Division failed."),
                () -> assertEquals(10, simpleCalculator.multiply(2, 5), "Multiplication failed."));
    }
}

Теперь, если вы запустите тесты в этом файле, вы сможете увидеть время каждого из тестов, поскольку вы расширили свой класс с помощью TimingExtension. Вы можете найти дополнительные расширения JUnit в их документации и создать свои собственные расширения, которые можно интегрировать в ваши тестовые классы, как показано выше.

17.1 JUnit Extensions

Часть 18. Выполнение теста

В этой статье мы рассмотрели множество тем, связанных с JUnit, но не обсудили, как можно проверить, написали ли вы достаточно тестов, чтобы охватить все ваши коды. IntelliJ IDEA предоставляет возможность узнать процент покрытия кода в вашем проекте. Чтобы узнать покрытие кода в вашем проекте, щелкните правой кнопкой мыши пакет java в разделе test и выберите параметр Запустить все тесты с покрытием.

18.1 Run Tests with Coverage

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

18.2 Code Coverage Percentages

Лучше, если вы сможете обеспечить 100% покрытие кода. Но лучше иметь хотя бы 80% покрытия кода.

Часть 19. Отчеты о тестировании Maven Surefire

Мы можем просмотреть сводку наших тестов в виде отчета, используя плагин Surefire Maven. Чтобы получить эту возможность создания отчетов, нам нужно немного обновить наш файл pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.nipunaupeksha</groupId>
    <artifactId>junit5</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit-platform.version>5.9.2</junit-platform.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit-platform.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit-platform.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit-platform.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.24.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>2.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.12.1</version>
            </plugin>
        </plugins>
    </build>
    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
        </plugins>
    </reporting>
</project>

Как вы можете заметить, нам нужно добавить maven-site-plugin и maven-surefire-report-plugin для создания сводного отчета о тестировании.

Дважды щелкните цель жизненного цикла site на вкладке Maven в IntelliJ IDEA или запустите mvn clean site, чтобы создать отчет о тестировании.

19.1 Running site Lifecycle Goal

Затем откройте каталог target и найдите файл index.html, расположенный в каталоге site. Откройте его в браузере, чтобы просмотреть отчет.

19.2 Open index.html file in Browser

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

19.3 Surefire Test Report

Часть 20. От JUnit 4 к JUnit 5

При переходе с JUnit 4 на JUnit 5 важно знать, что некоторые аннотации были изменены в JUnit 5. Некоторые из наиболее важных измененных аннотаций показаны ниже.

  • JUnit 4 @Before → JUnit 5 @BeforeEach
  • JUnit 4 @After → JUnit 5 @AfterEach
  • JUnit 4 @BeforeClass → JUnit 5 @BeforeAll
  • JUnit 4 @AfterClass → JUnit 5 @AfterAll
  • JUnit 4 @Ignored → JUnit 5 @Disabled
  • JUnit 4 @Category → JUnit 5 @Tag

Другие изменения можно найти в официальной документации JUnit 5.

Поскольку я использую IntelliJ IDEA, я всегда ссылался на Миграция с JUnit 4 на JUnit 5 ** автор: Хелен Скотт, каждый раз, когда мне нужно перейти на JUnit 5 с JUnit 4. Обязательно обновите свой pom.xml файл соответственно, если вы переходите на JUnit 5 с JUnit 4.

Заключение

Итак, вот оно! Вот как вы можете использовать JUnit с другими инструментами утверждения, такими как AssertJ или Hamcrest, для модульных тестов. Давайте поговорим о том, как вы можете использовать другие фреймворки, такие как Mockito и Spring Testing Framework, как-нибудь в другой раз. Вы можете найти проект, который мы использовали для этой статьи, здесь. Если у вас есть мысли или предложения для меня, всегда дайте мне знать.

https://github.com/nipunaupeksha/junit5-article?embedable=true


Оригинал